@burdenoff/microfe-ggnomad 2026.825.1 → 2026.828.1
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/ggnomad/components/AvailabilityPanel.d.ts +15 -0
- package/dist/ggnomad/components/AvailabilityPanel.d.ts.map +1 -0
- package/dist/ggnomad/components/AvailabilityPanel.js +91 -0
- package/dist/ggnomad/components/AvailabilityPanel.js.map +1 -0
- package/dist/ggnomad/components/BookingPanel.d.ts +1 -3
- package/dist/ggnomad/components/BookingPanel.d.ts.map +1 -1
- package/dist/ggnomad/components/BookingPanel.js +91 -82
- package/dist/ggnomad/components/BookingPanel.js.map +1 -1
- package/dist/ggnomad/components/EcoAccessPanel.d.ts +17 -0
- package/dist/ggnomad/components/EcoAccessPanel.d.ts.map +1 -0
- package/dist/ggnomad/components/EcoAccessPanel.js +68 -0
- package/dist/ggnomad/components/EcoAccessPanel.js.map +1 -0
- package/dist/ggnomad/components/FactGrid.d.ts +18 -0
- package/dist/ggnomad/components/FactGrid.d.ts.map +1 -0
- package/dist/ggnomad/components/FactGrid.js +29 -0
- package/dist/ggnomad/components/FactGrid.js.map +1 -0
- package/dist/ggnomad/components/GroupTripPanel.d.ts +22 -0
- package/dist/ggnomad/components/GroupTripPanel.d.ts.map +1 -0
- package/dist/ggnomad/components/GroupTripPanel.js +350 -0
- package/dist/ggnomad/components/GroupTripPanel.js.map +1 -0
- package/dist/ggnomad/components/ListingFilters.d.ts +29 -0
- package/dist/ggnomad/components/ListingFilters.d.ts.map +1 -0
- package/dist/ggnomad/components/ListingFilters.js +164 -0
- package/dist/ggnomad/components/ListingFilters.js.map +1 -0
- package/dist/ggnomad/components/PhotoGallery.d.ts +15 -0
- package/dist/ggnomad/components/PhotoGallery.d.ts.map +1 -0
- package/dist/ggnomad/components/PhotoGallery.js +54 -0
- package/dist/ggnomad/components/PhotoGallery.js.map +1 -0
- package/dist/ggnomad/components/ProviderCard.d.ts +19 -0
- package/dist/ggnomad/components/ProviderCard.d.ts.map +1 -0
- package/dist/ggnomad/components/ProviderCard.js +66 -0
- package/dist/ggnomad/components/ProviderCard.js.map +1 -0
- package/dist/ggnomad/components/RatingSummary.d.ts +14 -0
- package/dist/ggnomad/components/RatingSummary.d.ts.map +1 -0
- package/dist/ggnomad/components/RatingSummary.js +32 -0
- package/dist/ggnomad/components/RatingSummary.js.map +1 -0
- package/dist/ggnomad/components/TagChips.d.ts +16 -0
- package/dist/ggnomad/components/TagChips.d.ts.map +1 -0
- package/dist/ggnomad/components/TagChips.js +20 -0
- package/dist/ggnomad/components/TagChips.js.map +1 -0
- package/dist/ggnomad/components/index.d.ts +13 -0
- package/dist/ggnomad/components/index.d.ts.map +1 -1
- package/dist/ggnomad/components/index.js +9 -0
- package/dist/ggnomad/graphql.d.ts +10 -0
- package/dist/ggnomad/graphql.d.ts.map +1 -1
- package/dist/ggnomad/graphql.js +308 -20
- package/dist/ggnomad/graphql.js.map +1 -1
- package/dist/ggnomad/pages/ActivityDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/ActivityDetailsPage.js +174 -86
- package/dist/ggnomad/pages/ActivityDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/AnalyticsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/AnalyticsPage.js +109 -77
- package/dist/ggnomad/pages/AnalyticsPage.js.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/BookingsPage.js +188 -137
- package/dist/ggnomad/pages/BookingsPage.js.map +1 -1
- package/dist/ggnomad/pages/DashboardPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/DashboardPage.js +245 -128
- package/dist/ggnomad/pages/DashboardPage.js.map +1 -1
- package/dist/ggnomad/pages/EntertainmentPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/EntertainmentPage.js +115 -53
- package/dist/ggnomad/pages/EntertainmentPage.js.map +1 -1
- package/dist/ggnomad/pages/EventDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/EventDetailsPage.js +170 -87
- package/dist/ggnomad/pages/EventDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/FavoritesPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/FavoritesPage.js +201 -84
- package/dist/ggnomad/pages/FavoritesPage.js.map +1 -1
- package/dist/ggnomad/pages/HospitalityPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/HospitalityPage.js +99 -56
- package/dist/ggnomad/pages/HospitalityPage.js.map +1 -1
- package/dist/ggnomad/pages/PropertyDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/PropertyDetailsPage.js +177 -93
- package/dist/ggnomad/pages/PropertyDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/ProviderPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/ProviderPage.js +112 -63
- package/dist/ggnomad/pages/ProviderPage.js.map +1 -1
- package/dist/ggnomad/pages/SearchPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/SearchPage.js +236 -65
- package/dist/ggnomad/pages/SearchPage.js.map +1 -1
- package/dist/ggnomad/pages/TourDetailsPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TourDetailsPage.js +171 -86
- package/dist/ggnomad/pages/TourDetailsPage.js.map +1 -1
- package/dist/ggnomad/pages/TourismPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TourismPage.js +108 -54
- package/dist/ggnomad/pages/TourismPage.js.map +1 -1
- package/dist/ggnomad/pages/TravelPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TravelPage.js +406 -43
- package/dist/ggnomad/pages/TravelPage.js.map +1 -1
- package/dist/ggnomad/pages/TripPlanDetailPage.d.ts.map +1 -1
- package/dist/ggnomad/pages/TripPlanDetailPage.js +93 -70
- package/dist/ggnomad/pages/TripPlanDetailPage.js.map +1 -1
- package/dist/ggnomad/utils/format.d.ts +47 -0
- package/dist/ggnomad/utils/format.d.ts.map +1 -0
- package/dist/ggnomad/utils/format.js +125 -0
- package/dist/ggnomad/utils/format.js.map +1 -0
- package/dist/stories/BookingPanel.stories.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { humanizeEnum as e } from "../utils/format.js";
|
|
2
|
+
import { SectionCard as t } from "./SectionCard.js";
|
|
3
|
+
import { TagChips as n } from "./TagChips.js";
|
|
4
|
+
import { Accessibility as r, Leaf as i } from "lucide-react";
|
|
5
|
+
import { useI18n as a } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
6
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
7
|
+
//#region src/ggnomad/components/EcoAccessPanel.tsx
|
|
8
|
+
function c({ sustainabilityScore: c, sustainabilityPractices: l, accessibilityFeatures: u }) {
|
|
9
|
+
let { t: d } = a(), f = l.length > 0 || c > 0, p = u.length > 0;
|
|
10
|
+
return !f && !p ? null : /* @__PURE__ */ o(t, {
|
|
11
|
+
title: d("ggnomad.ecoAccess.title", "Sustainability & accessibility"),
|
|
12
|
+
children: /* @__PURE__ */ s("div", {
|
|
13
|
+
className: "space-y-5",
|
|
14
|
+
children: [
|
|
15
|
+
f ? /* @__PURE__ */ s("div", { children: [
|
|
16
|
+
/* @__PURE__ */ s("div", {
|
|
17
|
+
className: "flex items-center justify-between gap-3",
|
|
18
|
+
children: [/* @__PURE__ */ s("span", {
|
|
19
|
+
className: "inline-flex items-center gap-2 text-sm font-medium text-text-primary",
|
|
20
|
+
children: [/* @__PURE__ */ o(i, {
|
|
21
|
+
className: "size-4 text-status-success-text",
|
|
22
|
+
"aria-hidden": "true"
|
|
23
|
+
}), d("ggnomad.ecoAccess.sustainabilityScore", "Sustainability score")]
|
|
24
|
+
}), /* @__PURE__ */ s("span", {
|
|
25
|
+
className: "text-sm font-medium tabular-nums text-text-primary",
|
|
26
|
+
children: [c, "/100"]
|
|
27
|
+
})]
|
|
28
|
+
}),
|
|
29
|
+
/* @__PURE__ */ o("div", {
|
|
30
|
+
className: "mt-2 h-2 w-full overflow-hidden rounded-full bg-bg-sunken",
|
|
31
|
+
role: "progressbar",
|
|
32
|
+
"aria-valuenow": c,
|
|
33
|
+
"aria-valuemin": 0,
|
|
34
|
+
"aria-valuemax": 100,
|
|
35
|
+
"aria-label": d("ggnomad.ecoAccess.sustainabilityScore", "Sustainability score"),
|
|
36
|
+
children: /* @__PURE__ */ o("div", {
|
|
37
|
+
className: "h-full rounded-full bg-status-success-bg transition-[width] duration-500 ease-out",
|
|
38
|
+
style: { width: `${Math.min(100, Math.max(0, c))}%` }
|
|
39
|
+
})
|
|
40
|
+
}),
|
|
41
|
+
/* @__PURE__ */ o(n, {
|
|
42
|
+
className: "mt-3",
|
|
43
|
+
icon: /* @__PURE__ */ o(i, {}),
|
|
44
|
+
items: l.map(e)
|
|
45
|
+
})
|
|
46
|
+
] }) : null,
|
|
47
|
+
p ? /* @__PURE__ */ s("div", { children: [/* @__PURE__ */ s("p", {
|
|
48
|
+
className: "mb-2 inline-flex items-center gap-2 text-sm font-medium text-text-primary",
|
|
49
|
+
children: [/* @__PURE__ */ o(r, {
|
|
50
|
+
className: "size-4 text-action-primary-bg",
|
|
51
|
+
"aria-hidden": "true"
|
|
52
|
+
}), d("ggnomad.ecoAccess.accessibility", "Accessibility features")]
|
|
53
|
+
}), /* @__PURE__ */ o(n, {
|
|
54
|
+
icon: /* @__PURE__ */ o(r, {}),
|
|
55
|
+
items: u.map(e)
|
|
56
|
+
})] }) : null,
|
|
57
|
+
/* @__PURE__ */ o("p", {
|
|
58
|
+
className: "text-xs text-text-secondary",
|
|
59
|
+
children: d("ggnomad.ecoAccess.disclaimer", "Declared by the provider and not independently verified. The score is derived from the practices listed above.")
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { c as EcoAccessPanel };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=EcoAccessPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EcoAccessPanel.js","names":[],"sources":["../../../src/ggnomad/components/EcoAccessPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Accessibility, Leaf } from 'lucide-react';\n\nimport { humanizeEnum } from '../utils/format';\nimport { SectionCard } from './SectionCard';\nimport { TagChips } from './TagChips';\n\ninterface EcoAccessPanelProps {\n /** `sustainabilityScore: Int!` — deterministic 0-100 derived from the practices. */\n sustainabilityScore: number;\n /** `sustainabilityPractices: [GgnomadSustainabilityPractice!]!` */\n sustainabilityPractices: string[];\n /** `accessibilityFeatures: [GgnomadAccessibilityFeature!]!` */\n accessibilityFeatures: string[];\n}\n\n/**\n * BOFF-4364 / BOFF-4379 — the eco + accessibility attestations every listing type\n * carries. Both sets are **self-reported by the provider** and not independently\n * verified, which the panel states outright rather than presenting them as certified.\n * Renders nothing when the listing declares neither.\n */\nexport function EcoAccessPanel({\n sustainabilityScore,\n sustainabilityPractices,\n accessibilityFeatures,\n}: EcoAccessPanelProps) {\n const { t } = useI18n();\n const hasSustainability = sustainabilityPractices.length > 0 || sustainabilityScore > 0;\n const hasAccessibility = accessibilityFeatures.length > 0;\n\n if (!hasSustainability && !hasAccessibility) return null;\n\n return (\n <SectionCard title={t('ggnomad.ecoAccess.title', 'Sustainability & accessibility')}>\n <div className=\"space-y-5\">\n {hasSustainability ? (\n <div>\n <div className=\"flex items-center justify-between gap-3\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Leaf className=\"size-4 text-status-success-text\" aria-hidden=\"true\" />\n {t('ggnomad.ecoAccess.sustainabilityScore', 'Sustainability score')}\n </span>\n <span className=\"text-sm font-medium tabular-nums text-text-primary\">\n {sustainabilityScore}/100\n </span>\n </div>\n <div\n className=\"mt-2 h-2 w-full overflow-hidden rounded-full bg-bg-sunken\"\n role=\"progressbar\"\n aria-valuenow={sustainabilityScore}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={t('ggnomad.ecoAccess.sustainabilityScore', 'Sustainability score')}\n >\n <div\n className=\"h-full rounded-full bg-status-success-bg transition-[width] duration-500 ease-out\"\n style={{ width: `${Math.min(100, Math.max(0, sustainabilityScore))}%` }}\n />\n </div>\n <TagChips\n className=\"mt-3\"\n icon={<Leaf />}\n items={sustainabilityPractices.map(humanizeEnum)}\n />\n </div>\n ) : null}\n\n {hasAccessibility ? (\n <div>\n <p className=\"mb-2 inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Accessibility className=\"size-4 text-action-primary-bg\" aria-hidden=\"true\" />\n {t('ggnomad.ecoAccess.accessibility', 'Accessibility features')}\n </p>\n <TagChips icon={<Accessibility />} items={accessibilityFeatures.map(humanizeEnum)} />\n </div>\n ) : null}\n\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'ggnomad.ecoAccess.disclaimer',\n 'Declared by the provider and not independently verified. The score is derived from the practices listed above.'\n )}\n </p>\n </div>\n </SectionCard>\n );\n}\n"],"mappings":";;;;;;;AAsBA,SAAgB,EAAe,EAC7B,wBACA,4BACA,4BACsB;CACtB,IAAM,EAAE,SAAM,EAAQ,GAChB,IAAoB,EAAwB,SAAS,KAAK,IAAsB,GAChF,IAAmB,EAAsB,SAAS;CAIxD,OAFI,CAAC,KAAqB,CAAC,IAAyB,OAGlD,kBAAC,GAAD;EAAa,OAAO,EAAE,2BAA2B,gCAAgC;YAC/E,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,IACC,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACE,kBAAC,GAAD;QAAM,WAAU;QAAkC,eAAY;OAAQ,CAAA,GACrE,EAAE,yCAAyC,sBAAsB,CAC9D;UACN,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACG,GAAoB,MACjB;QACH;;KACL,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,iBAAe;MACf,iBAAe;MACf,iBAAe;MACf,cAAY,EAAE,yCAAyC,sBAAsB;gBAE7E,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,CAAmB,CAAC,EAAE,GAAG;MACvE,CAAA;KACE,CAAA;KACL,kBAAC,GAAD;MACE,WAAU;MACV,MAAM,kBAAC,GAAD,CAAO,CAAA;MACb,OAAO,EAAwB,IAAI,CAAY;KAChD,CAAA;IACE,EAAA,CAAA,IACH;IAEH,IACC,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eAAb,CACE,kBAAC,GAAD;MAAe,WAAU;MAAgC,eAAY;KAAQ,CAAA,GAC5E,EAAE,mCAAmC,wBAAwB,CAC7D;QACH,kBAAC,GAAD;KAAU,MAAM,kBAAC,GAAD,CAAgB,CAAA;KAAG,OAAO,EAAsB,IAAI,CAAY;IAAI,CAAA,CACjF,EAAA,CAAA,IACH;IAEJ,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,gCACA,gHACF;IACC,CAAA;GACA;;CACM,CAAA;AAEjB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface Fact {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
value: ReactNode;
|
|
7
|
+
icon?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The at-a-glance specification strip on a listing detail page (capacity, duration,
|
|
11
|
+
* check-in window, difficulty…). Callers filter out facts the record does not carry,
|
|
12
|
+
* so an absent value is simply not shown rather than rendered as "N/A".
|
|
13
|
+
*/
|
|
14
|
+
export declare function FactGrid({ facts, className }: {
|
|
15
|
+
facts: Fact[];
|
|
16
|
+
className?: string;
|
|
17
|
+
}): import("react").JSX.Element | null;
|
|
18
|
+
//# sourceMappingURL=FactGrid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FactGrid.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/FactGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;IAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,sCAyBnF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { jsx as t, jsxs as n } from "react/jsx-runtime";
|
|
3
|
+
//#region src/ggnomad/components/FactGrid.tsx
|
|
4
|
+
function r({ facts: r, className: i }) {
|
|
5
|
+
return r.length === 0 ? null : /* @__PURE__ */ t("dl", {
|
|
6
|
+
className: e("grid gap-3 sm:grid-cols-2 lg:grid-cols-4", i),
|
|
7
|
+
children: r.map((e) => /* @__PURE__ */ n("div", {
|
|
8
|
+
className: "flex items-start gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3",
|
|
9
|
+
children: [e.icon ? /* @__PURE__ */ t("span", {
|
|
10
|
+
className: "flex size-8 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary [&>svg]:size-4",
|
|
11
|
+
"aria-hidden": "true",
|
|
12
|
+
children: e.icon
|
|
13
|
+
}) : null, /* @__PURE__ */ n("div", {
|
|
14
|
+
className: "min-w-0",
|
|
15
|
+
children: [/* @__PURE__ */ t("dt", {
|
|
16
|
+
className: "text-xs text-text-secondary",
|
|
17
|
+
children: e.label
|
|
18
|
+
}), /* @__PURE__ */ t("dd", {
|
|
19
|
+
className: "mt-0.5 truncate font-medium text-text-primary",
|
|
20
|
+
children: e.value
|
|
21
|
+
})]
|
|
22
|
+
})]
|
|
23
|
+
}, e.id))
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { r as FactGrid };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=FactGrid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FactGrid.js","names":[],"sources":["../../../src/ggnomad/components/FactGrid.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\nimport { cn } from '../utils/cn';\n\nexport interface Fact {\n id: string;\n label: string;\n value: ReactNode;\n icon?: ReactNode;\n}\n\n/**\n * The at-a-glance specification strip on a listing detail page (capacity, duration,\n * check-in window, difficulty…). Callers filter out facts the record does not carry,\n * so an absent value is simply not shown rather than rendered as \"N/A\".\n */\nexport function FactGrid({ facts, className }: { facts: Fact[]; className?: string }) {\n if (facts.length === 0) return null;\n return (\n <dl className={cn('grid gap-3 sm:grid-cols-2 lg:grid-cols-4', className)}>\n {facts.map((fact) => (\n <div\n key={fact.id}\n className=\"flex items-start gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3\"\n >\n {fact.icon ? (\n <span\n className=\"flex size-8 flex-shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary [&>svg]:size-4\"\n aria-hidden=\"true\"\n >\n {fact.icon}\n </span>\n ) : null}\n <div className=\"min-w-0\">\n <dt className=\"text-xs text-text-secondary\">{fact.label}</dt>\n <dd className=\"mt-0.5 truncate font-medium text-text-primary\">{fact.value}</dd>\n </div>\n </div>\n ))}\n </dl>\n );\n}\n"],"mappings":";;;AAgBA,SAAgB,EAAS,EAAE,UAAO,gBAAoD;CAEpF,OADI,EAAM,WAAW,IAAU,OAE7B,kBAAC,MAAD;EAAI,WAAW,EAAG,4CAA4C,CAAS;YACpE,EAAM,KAAK,MACV,kBAAC,OAAD;GAEE,WAAU;aAFZ,CAIG,EAAK,OACJ,kBAAC,QAAD;IACE,WAAU;IACV,eAAY;cAEX,EAAK;GACF,CAAA,IACJ,MACJ,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA+B,EAAK;IAAU,CAAA,GAC5D,kBAAC,MAAD;KAAI,WAAU;eAAiD,EAAK;IAAU,CAAA,CAC3E;KACF;KAfE,EAAK,EAeP,CACN;CACC,CAAA;AAER"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface GroupTripItem {
|
|
2
|
+
id: string;
|
|
3
|
+
itemName: string;
|
|
4
|
+
price: number;
|
|
5
|
+
currency: string;
|
|
6
|
+
}
|
|
7
|
+
interface GroupTripPanelProps {
|
|
8
|
+
tripPlanId: string;
|
|
9
|
+
tripPlanName: string;
|
|
10
|
+
/** The trip plan's own items — the things members vote on. */
|
|
11
|
+
items: GroupTripItem[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* BOFF-4374 — turns a trip plan into a shared decision: who is travelling
|
|
15
|
+
* (`GgnomadGroupTrip.members`), what the group has voted for (`ggnomadGroupTripVotes`)
|
|
16
|
+
* and who owes what (`ggnomadGroupTripCostSplit`). Everything shown is a stored group
|
|
17
|
+
* trip record; nothing about settlement or payment status is displayed because the
|
|
18
|
+
* service tracks the split only, not who has actually paid.
|
|
19
|
+
*/
|
|
20
|
+
export declare function GroupTripPanel({ tripPlanId, tripPlanName, items }: GroupTripPanelProps): import("react").JSX.Element;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=GroupTripPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupTripPanel.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/GroupTripPanel.tsx"],"names":[],"mappings":"AAsEA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,mBAAmB;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAOD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,mBAAmB,+BA0dtF"}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { ADD_GGNOMAD_GROUP_TRIP_MEMBER_MUTATION as e, CREATE_GGNOMAD_GROUP_TRIP_MUTATION as t, GGNOMAD_GROUP_TRIP_COST_SPLIT_QUERY as n, GGNOMAD_GROUP_TRIP_VOTES_QUERY as r, GGNOMAD_MY_GROUP_TRIPS_QUERY as i, REMOVE_GGNOMAD_GROUP_TRIP_MEMBER_MUTATION as a, VOTE_GGNOMAD_TRIP_PLAN_ITEM_MUTATION as o } from "../graphql.js";
|
|
2
|
+
import { formatMoney as s } from "../utils/format.js";
|
|
3
|
+
import { SectionCard as c } from "./SectionCard.js";
|
|
4
|
+
import { notify as l } from "../utils/notify.js";
|
|
5
|
+
import { useState as u } from "react";
|
|
6
|
+
import { MinusCircle as d, ThumbsDown as f, ThumbsUp as p, UserPlus as m, Users as h, Wallet as g } from "lucide-react";
|
|
7
|
+
import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
8
|
+
import { useMutation as v, useQuery as y } from "@apollo/client/react";
|
|
9
|
+
import { Button as b, IllustratedEmptyState as x } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
11
|
+
//#region src/ggnomad/components/GroupTripPanel.tsx
|
|
12
|
+
var T = "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", E = [
|
|
13
|
+
"YES",
|
|
14
|
+
"NO",
|
|
15
|
+
"ABSTAIN"
|
|
16
|
+
];
|
|
17
|
+
function D({ tripPlanId: D, tripPlanName: O, items: k }) {
|
|
18
|
+
let { t: A } = _(), [j, M] = u("EVEN"), [N, P] = u({
|
|
19
|
+
actorId: "",
|
|
20
|
+
displayName: "",
|
|
21
|
+
email: ""
|
|
22
|
+
}), F = y(i), I = (F.data?.myGgnomadGroupTrips ?? []).find((e) => e.tripPlanId === D), L = y(n, {
|
|
23
|
+
variables: { groupTripId: I?.id ?? "" },
|
|
24
|
+
skip: !I
|
|
25
|
+
}), R = y(r, {
|
|
26
|
+
variables: { groupTripId: I?.id ?? "" },
|
|
27
|
+
skip: !I
|
|
28
|
+
}), [z, { loading: B }] = v(t), [V, { loading: H }] = v(e), [U, { loading: W }] = v(a), [G, { loading: K }] = v(o), q = async () => {
|
|
29
|
+
await Promise.all([
|
|
30
|
+
F.refetch(),
|
|
31
|
+
I ? L.refetch() : Promise.resolve(),
|
|
32
|
+
I ? R.refetch() : Promise.resolve()
|
|
33
|
+
]);
|
|
34
|
+
}, J = async () => {
|
|
35
|
+
try {
|
|
36
|
+
let e = await z({ variables: { input: {
|
|
37
|
+
name: O,
|
|
38
|
+
tripPlanId: D,
|
|
39
|
+
splitMethod: j
|
|
40
|
+
} } });
|
|
41
|
+
if (e.error) {
|
|
42
|
+
l.error(A("ggnomad.groupTrip.toast.createErrorTitle", "Could not start the group trip"), e.error.message);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
l.success(A("ggnomad.groupTrip.toast.createSuccessTitle", "Group trip started"), O), await F.refetch();
|
|
46
|
+
} catch (e) {
|
|
47
|
+
l.error(A("ggnomad.groupTrip.toast.createErrorTitle", "Could not start the group trip"), e instanceof Error ? e.message : String(e));
|
|
48
|
+
}
|
|
49
|
+
}, Y = async () => {
|
|
50
|
+
if (I) {
|
|
51
|
+
if (!N.actorId.trim() || !N.displayName.trim()) {
|
|
52
|
+
l.warning(A("ggnomad.groupTrip.toast.memberMissingTitle", "Actor ID and name required"), A("ggnomad.groupTrip.toast.memberMissingBody", "A member is identified by their actor ID; the display name is how they appear in the split."));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
let e = await V({ variables: {
|
|
57
|
+
groupTripId: I.id,
|
|
58
|
+
input: {
|
|
59
|
+
actorId: N.actorId.trim(),
|
|
60
|
+
displayName: N.displayName.trim(),
|
|
61
|
+
email: N.email.trim() || void 0
|
|
62
|
+
}
|
|
63
|
+
} });
|
|
64
|
+
if (e.error) {
|
|
65
|
+
l.error(A("ggnomad.groupTrip.toast.memberErrorTitle", "Could not add that member"), e.error.message);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
l.success(A("ggnomad.groupTrip.toast.memberAdded", "Member added"), N.displayName.trim()), P({
|
|
69
|
+
actorId: "",
|
|
70
|
+
displayName: "",
|
|
71
|
+
email: ""
|
|
72
|
+
}), await q();
|
|
73
|
+
} catch (e) {
|
|
74
|
+
l.error(A("ggnomad.groupTrip.toast.memberErrorTitle", "Could not add that member"), e instanceof Error ? e.message : String(e));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}, X = async (e) => {
|
|
78
|
+
if (I) try {
|
|
79
|
+
let t = await U({ variables: {
|
|
80
|
+
groupTripId: I.id,
|
|
81
|
+
memberId: e.id
|
|
82
|
+
} });
|
|
83
|
+
if (t.error) {
|
|
84
|
+
l.error(A("ggnomad.groupTrip.toast.removeErrorTitle", "Could not remove that member"), t.error.message);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
l.success(A("ggnomad.groupTrip.toast.memberRemoved", "Member removed"), e.displayName), await q();
|
|
88
|
+
} catch (e) {
|
|
89
|
+
l.error(A("ggnomad.groupTrip.toast.removeErrorTitle", "Could not remove that member"), e instanceof Error ? e.message : String(e));
|
|
90
|
+
}
|
|
91
|
+
}, Z = async (e, t) => {
|
|
92
|
+
if (I) try {
|
|
93
|
+
let n = await G({ variables: {
|
|
94
|
+
groupTripId: I.id,
|
|
95
|
+
input: {
|
|
96
|
+
tripPlanItemId: e,
|
|
97
|
+
choice: t
|
|
98
|
+
}
|
|
99
|
+
} });
|
|
100
|
+
if (n.error) {
|
|
101
|
+
l.error(A("ggnomad.groupTrip.toast.voteErrorTitle", "Could not record your vote"), n.error.message);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await R.refetch();
|
|
105
|
+
} catch (e) {
|
|
106
|
+
l.error(A("ggnomad.groupTrip.toast.voteErrorTitle", "Could not record your vote"), e instanceof Error ? e.message : String(e));
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
if (!I) return /* @__PURE__ */ w(c, {
|
|
110
|
+
title: A("ggnomad.groupTrip.title", "Group trip"),
|
|
111
|
+
children: [/* @__PURE__ */ C(x, {
|
|
112
|
+
illustration: "empty-people",
|
|
113
|
+
title: A("ggnomad.groupTrip.emptyTitle", "Travelling with other people?"),
|
|
114
|
+
description: A("ggnomad.groupTrip.emptyDescription", "Start a group trip on this plan to add travellers, collect votes on each itinerary item and split the cost.")
|
|
115
|
+
}), /* @__PURE__ */ w("div", {
|
|
116
|
+
className: "mt-4 grid gap-3 sm:grid-cols-2",
|
|
117
|
+
children: [/* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("label", {
|
|
118
|
+
htmlFor: "ggnomad-group-split",
|
|
119
|
+
className: "block text-sm font-medium text-text-primary",
|
|
120
|
+
children: A("ggnomad.groupTrip.splitMethod", "Cost split method")
|
|
121
|
+
}), /* @__PURE__ */ w("select", {
|
|
122
|
+
id: "ggnomad-group-split",
|
|
123
|
+
value: j,
|
|
124
|
+
onChange: (e) => M(e.target.value),
|
|
125
|
+
className: T,
|
|
126
|
+
children: [/* @__PURE__ */ C("option", {
|
|
127
|
+
value: "EVEN",
|
|
128
|
+
children: A("ggnomad.groupTrip.split.even", "Split evenly")
|
|
129
|
+
}), /* @__PURE__ */ C("option", {
|
|
130
|
+
value: "ITEMIZED",
|
|
131
|
+
children: A("ggnomad.groupTrip.split.itemized", "Itemised — per assigned item")
|
|
132
|
+
})]
|
|
133
|
+
})] }), /* @__PURE__ */ C("div", {
|
|
134
|
+
className: "flex items-end",
|
|
135
|
+
children: /* @__PURE__ */ w(b, {
|
|
136
|
+
onClick: () => void J(),
|
|
137
|
+
disabled: B,
|
|
138
|
+
children: [/* @__PURE__ */ C(h, {
|
|
139
|
+
className: "size-4",
|
|
140
|
+
"aria-hidden": "true"
|
|
141
|
+
}), B ? A("ggnomad.groupTrip.starting", "Starting…") : A("ggnomad.groupTrip.start", "Start group trip")]
|
|
142
|
+
})
|
|
143
|
+
})]
|
|
144
|
+
})]
|
|
145
|
+
});
|
|
146
|
+
let Q = L.data?.ggnomadGroupTripCostSplit, $ = R.data?.ggnomadGroupTripVotes ?? [], ee = new Map($.map((e) => [e.tripPlanItemId, e]));
|
|
147
|
+
return /* @__PURE__ */ w(S, { children: [
|
|
148
|
+
/* @__PURE__ */ w(c, {
|
|
149
|
+
title: A("ggnomad.groupTrip.travellers", "Travellers"),
|
|
150
|
+
action: /* @__PURE__ */ C("span", {
|
|
151
|
+
className: "text-sm tabular-nums text-text-secondary",
|
|
152
|
+
children: A("ggnomad.groupTrip.memberCount", "{{n}} in this group").replace("{{n}}", String(I.members.length))
|
|
153
|
+
}),
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ C("ul", {
|
|
156
|
+
className: "space-y-2",
|
|
157
|
+
children: I.members.map((e) => /* @__PURE__ */ w("li", {
|
|
158
|
+
className: "flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3",
|
|
159
|
+
children: [/* @__PURE__ */ w("div", {
|
|
160
|
+
className: "min-w-0",
|
|
161
|
+
children: [/* @__PURE__ */ w("p", {
|
|
162
|
+
className: "truncate font-medium text-text-primary",
|
|
163
|
+
children: [e.displayName, e.actorId === I.createdBy ? /* @__PURE__ */ C("span", {
|
|
164
|
+
className: "ml-2 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary",
|
|
165
|
+
children: A("ggnomad.groupTrip.organizer", "Organiser")
|
|
166
|
+
}) : null]
|
|
167
|
+
}), e.email ? /* @__PURE__ */ C("p", {
|
|
168
|
+
className: "truncate text-sm text-text-secondary",
|
|
169
|
+
children: e.email
|
|
170
|
+
}) : null]
|
|
171
|
+
}), e.actorId === I.createdBy ? null : /* @__PURE__ */ w("button", {
|
|
172
|
+
type: "button",
|
|
173
|
+
disabled: W,
|
|
174
|
+
onClick: () => void X(e),
|
|
175
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium text-text-secondary hover:bg-bg-sunken hover:text-status-error-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg disabled:opacity-60",
|
|
176
|
+
children: [/* @__PURE__ */ C(d, {
|
|
177
|
+
className: "size-3.5",
|
|
178
|
+
"aria-hidden": "true"
|
|
179
|
+
}), A("ggnomad.groupTrip.remove", "Remove")]
|
|
180
|
+
})]
|
|
181
|
+
}, e.id))
|
|
182
|
+
}),
|
|
183
|
+
/* @__PURE__ */ w("div", {
|
|
184
|
+
className: "mt-4 grid gap-3 border-t border-border-seam pt-4 sm:grid-cols-4",
|
|
185
|
+
children: [
|
|
186
|
+
/* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("label", {
|
|
187
|
+
htmlFor: "ggnomad-group-member-name",
|
|
188
|
+
className: "block text-sm font-medium text-text-primary",
|
|
189
|
+
children: A("ggnomad.groupTrip.memberName", "Display name")
|
|
190
|
+
}), /* @__PURE__ */ C("input", {
|
|
191
|
+
id: "ggnomad-group-member-name",
|
|
192
|
+
type: "text",
|
|
193
|
+
value: N.displayName,
|
|
194
|
+
onChange: (e) => P({
|
|
195
|
+
...N,
|
|
196
|
+
displayName: e.target.value
|
|
197
|
+
}),
|
|
198
|
+
className: T
|
|
199
|
+
})] }),
|
|
200
|
+
/* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("label", {
|
|
201
|
+
htmlFor: "ggnomad-group-member-actor",
|
|
202
|
+
className: "block text-sm font-medium text-text-primary",
|
|
203
|
+
children: A("ggnomad.groupTrip.memberActorId", "Actor ID")
|
|
204
|
+
}), /* @__PURE__ */ C("input", {
|
|
205
|
+
id: "ggnomad-group-member-actor",
|
|
206
|
+
type: "text",
|
|
207
|
+
value: N.actorId,
|
|
208
|
+
onChange: (e) => P({
|
|
209
|
+
...N,
|
|
210
|
+
actorId: e.target.value
|
|
211
|
+
}),
|
|
212
|
+
className: T
|
|
213
|
+
})] }),
|
|
214
|
+
/* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("label", {
|
|
215
|
+
htmlFor: "ggnomad-group-member-email",
|
|
216
|
+
className: "block text-sm font-medium text-text-primary",
|
|
217
|
+
children: A("ggnomad.groupTrip.memberEmail", "Email (optional)")
|
|
218
|
+
}), /* @__PURE__ */ C("input", {
|
|
219
|
+
id: "ggnomad-group-member-email",
|
|
220
|
+
type: "email",
|
|
221
|
+
value: N.email,
|
|
222
|
+
onChange: (e) => P({
|
|
223
|
+
...N,
|
|
224
|
+
email: e.target.value
|
|
225
|
+
}),
|
|
226
|
+
className: T
|
|
227
|
+
})] }),
|
|
228
|
+
/* @__PURE__ */ C("div", {
|
|
229
|
+
className: "flex items-end",
|
|
230
|
+
children: /* @__PURE__ */ w(b, {
|
|
231
|
+
variant: "outline",
|
|
232
|
+
onClick: () => void Y(),
|
|
233
|
+
disabled: H,
|
|
234
|
+
children: [/* @__PURE__ */ C(m, {
|
|
235
|
+
className: "size-4",
|
|
236
|
+
"aria-hidden": "true"
|
|
237
|
+
}), A("ggnomad.groupTrip.addMember", "Add traveller")]
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
]
|
|
241
|
+
}),
|
|
242
|
+
/* @__PURE__ */ C("p", {
|
|
243
|
+
className: "mt-2 text-xs text-text-secondary",
|
|
244
|
+
children: A("ggnomad.groupTrip.memberHint", "A traveller joins by their platform actor ID — the same identity the gateway authenticates them with.")
|
|
245
|
+
})
|
|
246
|
+
]
|
|
247
|
+
}),
|
|
248
|
+
/* @__PURE__ */ C(c, {
|
|
249
|
+
title: A("ggnomad.groupTrip.costSplit", "Cost split"),
|
|
250
|
+
action: /* @__PURE__ */ C("span", {
|
|
251
|
+
className: "text-sm text-text-secondary",
|
|
252
|
+
children: Q?.splitMethod === "ITEMIZED" ? A("ggnomad.groupTrip.split.itemizedShort", "Itemised") : A("ggnomad.groupTrip.split.evenShort", "Even")
|
|
253
|
+
}),
|
|
254
|
+
children: L.error ? /* @__PURE__ */ C("p", {
|
|
255
|
+
className: "text-sm text-status-error-text",
|
|
256
|
+
children: L.error.message
|
|
257
|
+
}) : Q ? /* @__PURE__ */ w(S, { children: [
|
|
258
|
+
/* @__PURE__ */ w("div", {
|
|
259
|
+
className: "mb-3 flex items-center justify-between gap-3",
|
|
260
|
+
children: [/* @__PURE__ */ w("span", {
|
|
261
|
+
className: "inline-flex items-center gap-2 text-sm text-text-secondary",
|
|
262
|
+
children: [/* @__PURE__ */ C(g, {
|
|
263
|
+
className: "size-4",
|
|
264
|
+
"aria-hidden": "true"
|
|
265
|
+
}), A("ggnomad.groupTrip.tripTotal", "Trip total")]
|
|
266
|
+
}), /* @__PURE__ */ C("span", {
|
|
267
|
+
className: "text-lg font-semibold tabular-nums text-text-primary",
|
|
268
|
+
children: s(Q.totalCost, Q.currency)
|
|
269
|
+
})]
|
|
270
|
+
}),
|
|
271
|
+
Q.shares.length === 0 ? /* @__PURE__ */ C("p", {
|
|
272
|
+
className: "text-sm text-text-secondary",
|
|
273
|
+
children: A("ggnomad.groupTrip.noShares", "Add travellers to see how the cost divides.")
|
|
274
|
+
}) : /* @__PURE__ */ C("ul", {
|
|
275
|
+
className: "space-y-2",
|
|
276
|
+
children: Q.shares.map((e) => /* @__PURE__ */ w("li", {
|
|
277
|
+
className: "flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3",
|
|
278
|
+
children: [/* @__PURE__ */ w("div", {
|
|
279
|
+
className: "min-w-0",
|
|
280
|
+
children: [/* @__PURE__ */ C("p", {
|
|
281
|
+
className: "truncate font-medium text-text-primary",
|
|
282
|
+
children: e.displayName
|
|
283
|
+
}), Q.splitMethod === "ITEMIZED" ? /* @__PURE__ */ C("p", {
|
|
284
|
+
className: "text-xs tabular-nums text-text-secondary",
|
|
285
|
+
children: A("ggnomad.groupTrip.shareBreakdown", "Assigned {{a}} · shared {{s}}").replace("{{a}}", s(e.assignedTotal, Q.currency)).replace("{{s}}", s(e.sharedTotal, Q.currency))
|
|
286
|
+
}) : null]
|
|
287
|
+
}), /* @__PURE__ */ C("span", {
|
|
288
|
+
className: "font-medium tabular-nums text-text-primary",
|
|
289
|
+
children: s(e.amountOwed, Q.currency)
|
|
290
|
+
})]
|
|
291
|
+
}, e.memberId))
|
|
292
|
+
}),
|
|
293
|
+
/* @__PURE__ */ C("p", {
|
|
294
|
+
className: "mt-3 text-xs text-text-secondary",
|
|
295
|
+
children: A("ggnomad.groupTrip.costSplitNote", "Each traveller’s share of the plan total. GGNomad tracks the split, not settlement — money movement happens outside the group trip.")
|
|
296
|
+
})
|
|
297
|
+
] }) : /* @__PURE__ */ C("p", {
|
|
298
|
+
className: "text-sm text-text-secondary",
|
|
299
|
+
children: A("ggnomad.groupTrip.costSplitLoading", "Working out the split…")
|
|
300
|
+
})
|
|
301
|
+
}),
|
|
302
|
+
/* @__PURE__ */ w(c, {
|
|
303
|
+
title: A("ggnomad.groupTrip.decisions", "Decisions & votes"),
|
|
304
|
+
children: [k.length === 0 ? /* @__PURE__ */ C("p", {
|
|
305
|
+
className: "text-sm text-text-secondary",
|
|
306
|
+
children: A("ggnomad.groupTrip.noItems", "Add itinerary items first — the group votes on those.")
|
|
307
|
+
}) : /* @__PURE__ */ C("ul", {
|
|
308
|
+
className: "space-y-3",
|
|
309
|
+
children: k.map((e) => {
|
|
310
|
+
let t = ee.get(e.id);
|
|
311
|
+
return /* @__PURE__ */ w("li", {
|
|
312
|
+
className: "flex flex-wrap items-center justify-between gap-4 rounded-lg border border-border-seam bg-bg-surface/40 p-3",
|
|
313
|
+
children: [/* @__PURE__ */ w("div", {
|
|
314
|
+
className: "min-w-0",
|
|
315
|
+
children: [/* @__PURE__ */ C("p", {
|
|
316
|
+
className: "truncate font-medium text-text-primary",
|
|
317
|
+
children: e.itemName
|
|
318
|
+
}), /* @__PURE__ */ w("p", {
|
|
319
|
+
className: "text-xs tabular-nums text-text-secondary",
|
|
320
|
+
children: [s(e.price, e.currency), t ? ` · ${A("ggnomad.groupTrip.tally", "{{yes}} yes · {{no}} no · {{abstain}} abstain").replace("{{yes}}", String(t.yes)).replace("{{no}}", String(t.no)).replace("{{abstain}}", String(t.abstain))}` : ` · ${A("ggnomad.groupTrip.noVotes", "no votes yet")}`]
|
|
321
|
+
})]
|
|
322
|
+
}), /* @__PURE__ */ C("div", {
|
|
323
|
+
className: "flex items-center gap-1.5",
|
|
324
|
+
children: E.map((t) => /* @__PURE__ */ w("button", {
|
|
325
|
+
type: "button",
|
|
326
|
+
disabled: K,
|
|
327
|
+
onClick: () => void Z(e.id, t),
|
|
328
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-border-seam px-2.5 py-1 text-sm text-text-primary transition-colors hover:bg-bg-sunken focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg disabled:opacity-60",
|
|
329
|
+
children: [t === "YES" ? /* @__PURE__ */ C(p, {
|
|
330
|
+
className: "size-3.5",
|
|
331
|
+
"aria-hidden": "true"
|
|
332
|
+
}) : t === "NO" ? /* @__PURE__ */ C(f, {
|
|
333
|
+
className: "size-3.5",
|
|
334
|
+
"aria-hidden": "true"
|
|
335
|
+
}) : null, t === "YES" ? A("ggnomad.groupTrip.vote.yes", "Yes") : t === "NO" ? A("ggnomad.groupTrip.vote.no", "No") : A("ggnomad.groupTrip.vote.abstain", "Abstain")]
|
|
336
|
+
}, t))
|
|
337
|
+
})]
|
|
338
|
+
}, e.id);
|
|
339
|
+
})
|
|
340
|
+
}), /* @__PURE__ */ C("p", {
|
|
341
|
+
className: "mt-3 text-xs text-text-secondary",
|
|
342
|
+
children: A("ggnomad.groupTrip.votesNote", "Voting records your own choice; re-voting replaces it. Tallies count every traveller’s current vote.")
|
|
343
|
+
})]
|
|
344
|
+
})
|
|
345
|
+
] });
|
|
346
|
+
}
|
|
347
|
+
//#endregion
|
|
348
|
+
export { D as GroupTripPanel };
|
|
349
|
+
|
|
350
|
+
//# sourceMappingURL=GroupTripPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupTripPanel.js","names":[],"sources":["../../../src/ggnomad/components/GroupTripPanel.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport type { TypedDocumentNode } from '@apollo/client';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { MinusCircle, ThumbsDown, ThumbsUp, UserPlus, Users, Wallet } from 'lucide-react';\nimport { Button, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\nimport {\n ADD_GGNOMAD_GROUP_TRIP_MEMBER_MUTATION,\n CREATE_GGNOMAD_GROUP_TRIP_MUTATION,\n GGNOMAD_GROUP_TRIP_COST_SPLIT_QUERY,\n GGNOMAD_GROUP_TRIP_VOTES_QUERY,\n GGNOMAD_MY_GROUP_TRIPS_QUERY,\n REMOVE_GGNOMAD_GROUP_TRIP_MEMBER_MUTATION,\n VOTE_GGNOMAD_TRIP_PLAN_ITEM_MUTATION,\n} from '../graphql';\nimport { formatMoney } from '../utils/format';\nimport { notify } from '../utils/notify';\nimport { SectionCard } from './SectionCard';\n\ntype SplitMethod = 'EVEN' | 'ITEMIZED';\ntype VoteChoice = 'YES' | 'NO' | 'ABSTAIN';\n\ninterface GroupTripMember {\n id: string;\n actorId: string;\n displayName: string;\n email?: string | null;\n createdAt: string;\n}\n\ninterface GroupTrip {\n id: string;\n name: string;\n tripPlanId: string;\n splitMethod: SplitMethod;\n currency: string;\n createdBy: string;\n createdAt: string;\n members: GroupTripMember[];\n}\n\ninterface CostSplit {\n currency: string;\n totalCost: number;\n splitMethod: SplitMethod;\n shares: Array<{\n memberId: string;\n actorId: string;\n displayName: string;\n amountOwed: number;\n assignedTotal: number;\n sharedTotal: number;\n }>;\n}\n\ninterface VoteTally {\n tripPlanItemId: string;\n yes: number;\n no: number;\n abstain: number;\n votes: Array<{\n id: string;\n actorId: string;\n choice: VoteChoice;\n comment?: string | null;\n createdAt: string;\n }>;\n}\n\nexport interface GroupTripItem {\n id: string;\n itemName: string;\n price: number;\n currency: string;\n}\n\ninterface GroupTripPanelProps {\n tripPlanId: string;\n tripPlanName: string;\n /** The trip plan's own items — the things members vote on. */\n items: GroupTripItem[];\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\nconst VOTE_CHOICES: VoteChoice[] = ['YES', 'NO', 'ABSTAIN'];\n\n/**\n * BOFF-4374 — turns a trip plan into a shared decision: who is travelling\n * (`GgnomadGroupTrip.members`), what the group has voted for (`ggnomadGroupTripVotes`)\n * and who owes what (`ggnomadGroupTripCostSplit`). Everything shown is a stored group\n * trip record; nothing about settlement or payment status is displayed because the\n * service tracks the split only, not who has actually paid.\n */\nexport function GroupTripPanel({ tripPlanId, tripPlanName, items }: GroupTripPanelProps) {\n const { t } = useI18n();\n const [splitMethod, setSplitMethod] = useState<SplitMethod>('EVEN');\n const [newMember, setNewMember] = useState({ actorId: '', displayName: '', email: '' });\n\n const groupTripsQuery = useQuery(\n GGNOMAD_MY_GROUP_TRIPS_QUERY as TypedDocumentNode<\n { myGgnomadGroupTrips: GroupTrip[] },\n Record<string, never>\n >\n );\n const groupTrip = (groupTripsQuery.data?.myGgnomadGroupTrips ?? []).find(\n (candidate) => candidate.tripPlanId === tripPlanId\n );\n\n const costSplitQuery = useQuery(\n GGNOMAD_GROUP_TRIP_COST_SPLIT_QUERY as TypedDocumentNode<\n { ggnomadGroupTripCostSplit: CostSplit },\n { groupTripId: string }\n >,\n { variables: { groupTripId: groupTrip?.id ?? '' }, skip: !groupTrip }\n );\n\n const votesQuery = useQuery(\n GGNOMAD_GROUP_TRIP_VOTES_QUERY as TypedDocumentNode<\n { ggnomadGroupTripVotes: VoteTally[] },\n { groupTripId: string }\n >,\n { variables: { groupTripId: groupTrip?.id ?? '' }, skip: !groupTrip }\n );\n\n const [createGroupTrip, { loading: creating }] = useMutation(\n CREATE_GGNOMAD_GROUP_TRIP_MUTATION as TypedDocumentNode<\n { createGgnomadGroupTrip: { id: string; name: string } },\n { input: { name: string; tripPlanId: string; splitMethod: SplitMethod } }\n >\n );\n const [addMember, { loading: addingMember }] = useMutation(\n ADD_GGNOMAD_GROUP_TRIP_MEMBER_MUTATION as TypedDocumentNode<\n { addGgnomadGroupTripMember: { id: string; displayName: string } },\n { groupTripId: string; input: { actorId: string; displayName: string; email?: string } }\n >\n );\n const [removeMember, { loading: removingMember }] = useMutation(\n REMOVE_GGNOMAD_GROUP_TRIP_MEMBER_MUTATION as TypedDocumentNode<\n { removeGgnomadGroupTripMember: boolean },\n { groupTripId: string; memberId: string }\n >\n );\n const [voteItem, { loading: voting }] = useMutation(\n VOTE_GGNOMAD_TRIP_PLAN_ITEM_MUTATION as TypedDocumentNode<\n { voteGgnomadTripPlanItem: { id: string; choice: VoteChoice } },\n { groupTripId: string; input: { tripPlanItemId: string; choice: VoteChoice } }\n >\n );\n\n const refreshAll = async () => {\n await Promise.all([\n groupTripsQuery.refetch(),\n groupTrip ? costSplitQuery.refetch() : Promise.resolve(),\n groupTrip ? votesQuery.refetch() : Promise.resolve(),\n ]);\n };\n\n const handleCreate = async () => {\n try {\n const result = await createGroupTrip({\n variables: { input: { name: tripPlanName, tripPlanId, splitMethod } },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.groupTrip.toast.createErrorTitle', 'Could not start the group trip'),\n result.error.message\n );\n return;\n }\n notify.success(\n t('ggnomad.groupTrip.toast.createSuccessTitle', 'Group trip started'),\n tripPlanName\n );\n await groupTripsQuery.refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.groupTrip.toast.createErrorTitle', 'Could not start the group trip'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const handleAddMember = async () => {\n if (!groupTrip) return;\n if (!newMember.actorId.trim() || !newMember.displayName.trim()) {\n notify.warning(\n t('ggnomad.groupTrip.toast.memberMissingTitle', 'Actor ID and name required'),\n t(\n 'ggnomad.groupTrip.toast.memberMissingBody',\n 'A member is identified by their actor ID; the display name is how they appear in the split.'\n )\n );\n return;\n }\n try {\n const result = await addMember({\n variables: {\n groupTripId: groupTrip.id,\n input: {\n actorId: newMember.actorId.trim(),\n displayName: newMember.displayName.trim(),\n email: newMember.email.trim() || undefined,\n },\n },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.groupTrip.toast.memberErrorTitle', 'Could not add that member'),\n result.error.message\n );\n return;\n }\n notify.success(\n t('ggnomad.groupTrip.toast.memberAdded', 'Member added'),\n newMember.displayName.trim()\n );\n setNewMember({ actorId: '', displayName: '', email: '' });\n await refreshAll();\n } catch (err) {\n notify.error(\n t('ggnomad.groupTrip.toast.memberErrorTitle', 'Could not add that member'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const handleRemoveMember = async (member: GroupTripMember) => {\n if (!groupTrip) return;\n try {\n const result = await removeMember({\n variables: { groupTripId: groupTrip.id, memberId: member.id },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.groupTrip.toast.removeErrorTitle', 'Could not remove that member'),\n result.error.message\n );\n return;\n }\n notify.success(\n t('ggnomad.groupTrip.toast.memberRemoved', 'Member removed'),\n member.displayName\n );\n await refreshAll();\n } catch (err) {\n notify.error(\n t('ggnomad.groupTrip.toast.removeErrorTitle', 'Could not remove that member'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n const handleVote = async (tripPlanItemId: string, choice: VoteChoice) => {\n if (!groupTrip) return;\n try {\n const result = await voteItem({\n variables: { groupTripId: groupTrip.id, input: { tripPlanItemId, choice } },\n });\n if (result.error) {\n notify.error(\n t('ggnomad.groupTrip.toast.voteErrorTitle', 'Could not record your vote'),\n result.error.message\n );\n return;\n }\n await votesQuery.refetch();\n } catch (err) {\n notify.error(\n t('ggnomad.groupTrip.toast.voteErrorTitle', 'Could not record your vote'),\n err instanceof Error ? err.message : String(err)\n );\n }\n };\n\n if (!groupTrip) {\n return (\n <SectionCard title={t('ggnomad.groupTrip.title', 'Group trip')}>\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={t('ggnomad.groupTrip.emptyTitle', 'Travelling with other people?')}\n description={t(\n 'ggnomad.groupTrip.emptyDescription',\n 'Start a group trip on this plan to add travellers, collect votes on each itinerary item and split the cost.'\n )}\n />\n <div className=\"mt-4 grid gap-3 sm:grid-cols-2\">\n <div>\n <label\n htmlFor=\"ggnomad-group-split\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.groupTrip.splitMethod', 'Cost split method')}\n </label>\n <select\n id=\"ggnomad-group-split\"\n value={splitMethod}\n onChange={(event) => setSplitMethod(event.target.value as SplitMethod)}\n className={INPUT_CLASSES}\n >\n <option value=\"EVEN\">{t('ggnomad.groupTrip.split.even', 'Split evenly')}</option>\n <option value=\"ITEMIZED\">\n {t('ggnomad.groupTrip.split.itemized', 'Itemised — per assigned item')}\n </option>\n </select>\n </div>\n <div className=\"flex items-end\">\n <Button onClick={() => void handleCreate()} disabled={creating}>\n <Users className=\"size-4\" aria-hidden=\"true\" />\n {creating\n ? t('ggnomad.groupTrip.starting', 'Starting…')\n : t('ggnomad.groupTrip.start', 'Start group trip')}\n </Button>\n </div>\n </div>\n </SectionCard>\n );\n }\n\n const costSplit = costSplitQuery.data?.ggnomadGroupTripCostSplit;\n const tallies = votesQuery.data?.ggnomadGroupTripVotes ?? [];\n const talliesByItem = new Map(tallies.map((tally) => [tally.tripPlanItemId, tally]));\n\n return (\n <>\n <SectionCard\n title={t('ggnomad.groupTrip.travellers', 'Travellers')}\n action={\n <span className=\"text-sm tabular-nums text-text-secondary\">\n {t('ggnomad.groupTrip.memberCount', '{{n}} in this group').replace(\n '{{n}}',\n String(groupTrip.members.length)\n )}\n </span>\n }\n >\n <ul className=\"space-y-2\">\n {groupTrip.members.map((member) => (\n <li\n key={member.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">\n {member.displayName}\n {member.actorId === groupTrip.createdBy ? (\n <span className=\"ml-2 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary\">\n {t('ggnomad.groupTrip.organizer', 'Organiser')}\n </span>\n ) : null}\n </p>\n {member.email ? (\n <p className=\"truncate text-sm text-text-secondary\">{member.email}</p>\n ) : null}\n </div>\n {member.actorId === groupTrip.createdBy ? null : (\n <button\n type=\"button\"\n disabled={removingMember}\n onClick={() => void handleRemoveMember(member)}\n className=\"inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium text-text-secondary hover:bg-bg-sunken hover:text-status-error-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg disabled:opacity-60\"\n >\n <MinusCircle className=\"size-3.5\" aria-hidden=\"true\" />\n {t('ggnomad.groupTrip.remove', 'Remove')}\n </button>\n )}\n </li>\n ))}\n </ul>\n\n <div className=\"mt-4 grid gap-3 border-t border-border-seam pt-4 sm:grid-cols-4\">\n <div>\n <label\n htmlFor=\"ggnomad-group-member-name\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.groupTrip.memberName', 'Display name')}\n </label>\n <input\n id=\"ggnomad-group-member-name\"\n type=\"text\"\n value={newMember.displayName}\n onChange={(event) => setNewMember({ ...newMember, displayName: event.target.value })}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"ggnomad-group-member-actor\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.groupTrip.memberActorId', 'Actor ID')}\n </label>\n <input\n id=\"ggnomad-group-member-actor\"\n type=\"text\"\n value={newMember.actorId}\n onChange={(event) => setNewMember({ ...newMember, actorId: event.target.value })}\n className={INPUT_CLASSES}\n />\n </div>\n <div>\n <label\n htmlFor=\"ggnomad-group-member-email\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n {t('ggnomad.groupTrip.memberEmail', 'Email (optional)')}\n </label>\n <input\n id=\"ggnomad-group-member-email\"\n type=\"email\"\n value={newMember.email}\n onChange={(event) => setNewMember({ ...newMember, email: event.target.value })}\n className={INPUT_CLASSES}\n />\n </div>\n <div className=\"flex items-end\">\n <Button\n variant=\"outline\"\n onClick={() => void handleAddMember()}\n disabled={addingMember}\n >\n <UserPlus className=\"size-4\" aria-hidden=\"true\" />\n {t('ggnomad.groupTrip.addMember', 'Add traveller')}\n </Button>\n </div>\n </div>\n <p className=\"mt-2 text-xs text-text-secondary\">\n {t(\n 'ggnomad.groupTrip.memberHint',\n 'A traveller joins by their platform actor ID — the same identity the gateway authenticates them with.'\n )}\n </p>\n </SectionCard>\n\n <SectionCard\n title={t('ggnomad.groupTrip.costSplit', 'Cost split')}\n action={\n <span className=\"text-sm text-text-secondary\">\n {costSplit?.splitMethod === 'ITEMIZED'\n ? t('ggnomad.groupTrip.split.itemizedShort', 'Itemised')\n : t('ggnomad.groupTrip.split.evenShort', 'Even')}\n </span>\n }\n >\n {costSplitQuery.error ? (\n <p className=\"text-sm text-status-error-text\">{costSplitQuery.error.message}</p>\n ) : !costSplit ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.groupTrip.costSplitLoading', 'Working out the split…')}\n </p>\n ) : (\n <>\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <span className=\"inline-flex items-center gap-2 text-sm text-text-secondary\">\n <Wallet className=\"size-4\" aria-hidden=\"true\" />\n {t('ggnomad.groupTrip.tripTotal', 'Trip total')}\n </span>\n <span className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {formatMoney(costSplit.totalCost, costSplit.currency)}\n </span>\n </div>\n {costSplit.shares.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {t('ggnomad.groupTrip.noShares', 'Add travellers to see how the cost divides.')}\n </p>\n ) : (\n <ul className=\"space-y-2\">\n {costSplit.shares.map((share) => (\n <li\n key={share.memberId}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border-seam bg-bg-surface/40 p-3\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{share.displayName}</p>\n {costSplit.splitMethod === 'ITEMIZED' ? (\n <p className=\"text-xs tabular-nums text-text-secondary\">\n {t('ggnomad.groupTrip.shareBreakdown', 'Assigned {{a}} · shared {{s}}')\n .replace('{{a}}', formatMoney(share.assignedTotal, costSplit.currency))\n .replace('{{s}}', formatMoney(share.sharedTotal, costSplit.currency))}\n </p>\n ) : null}\n </div>\n <span className=\"font-medium tabular-nums text-text-primary\">\n {formatMoney(share.amountOwed, costSplit.currency)}\n </span>\n </li>\n ))}\n </ul>\n )}\n <p className=\"mt-3 text-xs text-text-secondary\">\n {t(\n 'ggnomad.groupTrip.costSplitNote',\n 'Each traveller’s share of the plan total. GGNomad tracks the split, not settlement — money movement happens outside the group trip.'\n )}\n </p>\n </>\n )}\n </SectionCard>\n\n <SectionCard title={t('ggnomad.groupTrip.decisions', 'Decisions & votes')}>\n {items.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {t(\n 'ggnomad.groupTrip.noItems',\n 'Add itinerary items first — the group votes on those.'\n )}\n </p>\n ) : (\n <ul className=\"space-y-3\">\n {items.map((item) => {\n const tally = talliesByItem.get(item.id);\n return (\n <li\n key={item.id}\n className=\"flex flex-wrap items-center justify-between gap-4 rounded-lg border border-border-seam bg-bg-surface/40 p-3\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{item.itemName}</p>\n <p className=\"text-xs tabular-nums text-text-secondary\">\n {formatMoney(item.price, item.currency)}\n {tally\n ? ` · ${t(\n 'ggnomad.groupTrip.tally',\n '{{yes}} yes · {{no}} no · {{abstain}} abstain'\n )\n .replace('{{yes}}', String(tally.yes))\n .replace('{{no}}', String(tally.no))\n .replace('{{abstain}}', String(tally.abstain))}`\n : ` · ${t('ggnomad.groupTrip.noVotes', 'no votes yet')}`}\n </p>\n </div>\n <div className=\"flex items-center gap-1.5\">\n {VOTE_CHOICES.map((choice) => (\n <button\n key={choice}\n type=\"button\"\n disabled={voting}\n onClick={() => void handleVote(item.id, choice)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-seam px-2.5 py-1 text-sm text-text-primary transition-colors hover:bg-bg-sunken focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-bg disabled:opacity-60\"\n >\n {choice === 'YES' ? (\n <ThumbsUp className=\"size-3.5\" aria-hidden=\"true\" />\n ) : choice === 'NO' ? (\n <ThumbsDown className=\"size-3.5\" aria-hidden=\"true\" />\n ) : null}\n {choice === 'YES'\n ? t('ggnomad.groupTrip.vote.yes', 'Yes')\n : choice === 'NO'\n ? t('ggnomad.groupTrip.vote.no', 'No')\n : t('ggnomad.groupTrip.vote.abstain', 'Abstain')}\n </button>\n ))}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n <p className=\"mt-3 text-xs text-text-secondary\">\n {t(\n 'ggnomad.groupTrip.votesNote',\n 'Voting records your own choice; re-voting replaces it. Tallies count every traveller’s current vote.'\n )}\n </p>\n </SectionCard>\n </>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ListingStatusFilter = 'ACTIVE' | 'PENDING_REVIEW' | 'DRAFT' | 'PAUSED' | 'ARCHIVED';
|
|
2
|
+
/** Every value of `GgnomadAccessibilityFeature` on the service schema (BOFF-4379). */
|
|
3
|
+
export declare const ACCESSIBILITY_FEATURES: readonly ["WHEELCHAIR_ACCESSIBLE", "STEP_FREE_ENTRANCE", "ELEVATOR_OR_LIFT", "ACCESSIBLE_BEDROOM", "ACCESSIBLE_RESTROOM", "ROLL_IN_SHOWER", "ACCESSIBLE_PARKING", "SERVICE_ANIMAL_FRIENDLY", "HEARING_AIDS", "VISUAL_AIDS"];
|
|
4
|
+
export interface ListingFilterState {
|
|
5
|
+
/** `status` argument — undefined means "every status". */
|
|
6
|
+
status?: ListingStatusFilter;
|
|
7
|
+
/** `minSustainabilityScore` argument (0-100). */
|
|
8
|
+
minSustainabilityScore?: number;
|
|
9
|
+
/** `hasAccessibilityFeatures` argument — a listing must declare ALL of them. */
|
|
10
|
+
accessibilityFeatures: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare const EMPTY_LISTING_FILTERS: ListingFilterState;
|
|
13
|
+
export declare function hasActiveListingFilters(value: ListingFilterState): boolean;
|
|
14
|
+
interface ListingFiltersProps {
|
|
15
|
+
idPrefix: string;
|
|
16
|
+
value: ListingFilterState;
|
|
17
|
+
onChange: (next: ListingFilterState) => void;
|
|
18
|
+
/** Number of rows currently rendered, shown next to the filter summary. */
|
|
19
|
+
resultCount: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Catalogue filter bar wired to the three filter arguments `ggnomadProperties`,
|
|
23
|
+
* `ggnomadTours`, `ggnomadEvents` and `ggnomadActivities` actually accept —
|
|
24
|
+
* `status`, `minSustainabilityScore` and `hasAccessibilityFeatures`. Every control
|
|
25
|
+
* re-runs the query server-side; nothing here filters a page-local slice.
|
|
26
|
+
*/
|
|
27
|
+
export declare function ListingFilters({ idPrefix, value, onChange, resultCount }: ListingFiltersProps): import("react").JSX.Element;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=ListingFilters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListingFilters.d.ts","sourceRoot":"","sources":["../../../src/ggnomad/components/ListingFilters.tsx"],"names":[],"mappings":"AAQA,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,gBAAgB,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEhG,sFAAsF;AACtF,eAAO,MAAM,sBAAsB,6NAWzB,CAAC;AAMX,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,iDAAiD;IACjD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gFAAgF;IAChF,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,eAAO,MAAM,qBAAqB,EAAE,kBAAkD,CAAC;AAEvF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAM1E;AAKD,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC7C,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,mBAAmB,+BAuK7F"}
|