@burdenoff/microfe-planmagnet 2026.928.2 → 2026.928.4
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/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +102 -84
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +103 -55
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/controls/CompetitorControls.d.ts +20 -0
- package/dist/planmagnet/controls/CompetitorControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/CompetitorControls.js +172 -0
- package/dist/planmagnet/controls/CompetitorControls.js.map +1 -0
- package/dist/planmagnet/controls/FeatureControls.js +29 -29
- package/dist/planmagnet/controls/FeatureTriage.js +11 -11
- package/dist/planmagnet/controls/ObjectiveControls.js +24 -24
- package/dist/planmagnet/controls/Pickers.d.ts +17 -2
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +100 -9
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/controls/PortfolioControls.d.ts +13 -0
- package/dist/planmagnet/controls/PortfolioControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/PortfolioControls.js +114 -0
- package/dist/planmagnet/controls/PortfolioControls.js.map +1 -0
- package/dist/planmagnet/controls/ProgramControls.d.ts +25 -0
- package/dist/planmagnet/controls/ProgramControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/ProgramControls.js +187 -0
- package/dist/planmagnet/controls/ProgramControls.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js +127 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js +129 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js +132 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js +181 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.js +242 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js +302 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +335 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js +154 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +124 -138
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js +221 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js +279 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.js +178 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.js +157 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +130 -103
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +156 -136
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksImportPagePage.js +5 -5
- package/dist/planmagnet/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { detailPath as t } from "../utils/rowNavigation.js";
|
|
3
|
+
import { CompetitorHeaderActions as n } from "../controls/CompetitorControls.js";
|
|
4
|
+
import { Loader2 as r, Swords as i } from "lucide-react";
|
|
5
|
+
import { useParams as a } from "react-router";
|
|
6
|
+
import { gql as o } from "@apollo/client";
|
|
7
|
+
import { useQuery as s } from "@apollo/client/react";
|
|
8
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
9
|
+
import { Badge as u, Card as d, CardContent as f, CardHeader as p, CardTitle as m } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
//#region src/planmagnet/pages/CompetitorsCompetitorDetailPagePage.tsx
|
|
11
|
+
var h = o`
|
|
12
|
+
query GetCompetitor($id: ID!) {
|
|
13
|
+
planmagnetCompetitor(id: $id) {
|
|
14
|
+
id
|
|
15
|
+
workspaceId
|
|
16
|
+
name
|
|
17
|
+
website
|
|
18
|
+
description
|
|
19
|
+
tags
|
|
20
|
+
createdBy
|
|
21
|
+
createdAt
|
|
22
|
+
updatedAt
|
|
23
|
+
featureRequests {
|
|
24
|
+
id
|
|
25
|
+
title
|
|
26
|
+
status
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
function g() {
|
|
32
|
+
let o = e(), g = a().id ?? "", { data: _, loading: v, error: y } = s(h, {
|
|
33
|
+
variables: { id: g },
|
|
34
|
+
skip: !g
|
|
35
|
+
});
|
|
36
|
+
if (v && !_) return /* @__PURE__ */ c("div", {
|
|
37
|
+
className: "flex items-center justify-center p-12",
|
|
38
|
+
children: /* @__PURE__ */ c(r, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
39
|
+
});
|
|
40
|
+
if (y) return /* @__PURE__ */ l("div", {
|
|
41
|
+
className: "p-6 text-destructive",
|
|
42
|
+
children: ["Failed to load competitor: ", y.message]
|
|
43
|
+
});
|
|
44
|
+
let b = _?.planmagnetCompetitor;
|
|
45
|
+
return b ? /* @__PURE__ */ l("div", {
|
|
46
|
+
className: "space-y-6 p-6",
|
|
47
|
+
children: [/* @__PURE__ */ l("div", {
|
|
48
|
+
className: "flex items-center justify-between",
|
|
49
|
+
children: [/* @__PURE__ */ l("div", { children: [/* @__PURE__ */ l("div", {
|
|
50
|
+
className: "flex items-center gap-2",
|
|
51
|
+
children: [/* @__PURE__ */ c(i, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ c("h1", {
|
|
52
|
+
className: "text-2xl font-semibold",
|
|
53
|
+
children: b.name
|
|
54
|
+
})]
|
|
55
|
+
}), b.website && /* @__PURE__ */ c("p", {
|
|
56
|
+
className: "text-sm text-primary mt-1",
|
|
57
|
+
children: b.website
|
|
58
|
+
})] }), /* @__PURE__ */ c(n, {
|
|
59
|
+
competitorId: b.id,
|
|
60
|
+
name: b.name
|
|
61
|
+
})]
|
|
62
|
+
}), /* @__PURE__ */ l("div", {
|
|
63
|
+
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
64
|
+
children: [/* @__PURE__ */ l("div", {
|
|
65
|
+
className: "lg:col-span-2 space-y-6",
|
|
66
|
+
children: [/* @__PURE__ */ l(d, { children: [/* @__PURE__ */ c(p, { children: /* @__PURE__ */ c(m, { children: "Notes" }) }), /* @__PURE__ */ c(f, { children: /* @__PURE__ */ c("p", {
|
|
67
|
+
className: "text-sm",
|
|
68
|
+
children: b.description ?? "No notes yet."
|
|
69
|
+
}) })] }), /* @__PURE__ */ l(d, { children: [/* @__PURE__ */ c(p, { children: /* @__PURE__ */ l(m, { children: [
|
|
70
|
+
"Linked Feature Requests (",
|
|
71
|
+
b.featureRequests.length,
|
|
72
|
+
")"
|
|
73
|
+
] }) }), /* @__PURE__ */ l(f, {
|
|
74
|
+
className: "space-y-2",
|
|
75
|
+
children: [b.featureRequests.length === 0 && /* @__PURE__ */ c("p", {
|
|
76
|
+
className: "text-sm text-muted-foreground",
|
|
77
|
+
children: "Not linked to any feature request yet. Add it from a feature request’s detail page."
|
|
78
|
+
}), b.featureRequests.map((e) => /* @__PURE__ */ l("button", {
|
|
79
|
+
type: "button",
|
|
80
|
+
className: "flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted",
|
|
81
|
+
onClick: () => o(t("/products/feature-detail-page", e.id)),
|
|
82
|
+
children: [/* @__PURE__ */ c("span", {
|
|
83
|
+
className: "text-sm font-medium",
|
|
84
|
+
children: e.title
|
|
85
|
+
}), /* @__PURE__ */ c(u, {
|
|
86
|
+
variant: "outline",
|
|
87
|
+
className: "text-xs capitalize",
|
|
88
|
+
children: e.status.toLowerCase().replace("_", " ")
|
|
89
|
+
})]
|
|
90
|
+
}, e.id))]
|
|
91
|
+
})] })]
|
|
92
|
+
}), /* @__PURE__ */ l("div", {
|
|
93
|
+
className: "space-y-6",
|
|
94
|
+
children: [/* @__PURE__ */ l(d, { children: [/* @__PURE__ */ c(p, { children: /* @__PURE__ */ c(m, { children: "Details" }) }), /* @__PURE__ */ l(f, {
|
|
95
|
+
className: "space-y-2 text-sm",
|
|
96
|
+
children: [/* @__PURE__ */ l("div", {
|
|
97
|
+
className: "flex justify-between",
|
|
98
|
+
children: [/* @__PURE__ */ c("span", {
|
|
99
|
+
className: "text-muted-foreground",
|
|
100
|
+
children: "Created"
|
|
101
|
+
}), /* @__PURE__ */ c("span", { children: new Date(b.createdAt).toLocaleDateString() })]
|
|
102
|
+
}), /* @__PURE__ */ l("div", {
|
|
103
|
+
className: "flex justify-between",
|
|
104
|
+
children: [/* @__PURE__ */ c("span", {
|
|
105
|
+
className: "text-muted-foreground",
|
|
106
|
+
children: "Updated"
|
|
107
|
+
}), /* @__PURE__ */ c("span", { children: new Date(b.updatedAt).toLocaleDateString() })]
|
|
108
|
+
})]
|
|
109
|
+
})] }), b.tags.length > 0 && /* @__PURE__ */ l(d, { children: [/* @__PURE__ */ c(p, { children: /* @__PURE__ */ c(m, { children: "Tags" }) }), /* @__PURE__ */ c(f, {
|
|
110
|
+
className: "flex flex-wrap gap-1",
|
|
111
|
+
children: b.tags.map((e) => /* @__PURE__ */ c(u, {
|
|
112
|
+
variant: "outline",
|
|
113
|
+
className: "text-xs",
|
|
114
|
+
children: e
|
|
115
|
+
}, e))
|
|
116
|
+
})] })]
|
|
117
|
+
})]
|
|
118
|
+
})]
|
|
119
|
+
}) : /* @__PURE__ */ c("div", {
|
|
120
|
+
className: "p-6 text-muted-foreground",
|
|
121
|
+
children: "Competitor not found."
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { g as CompetitorsCompetitorDetailPagePage };
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=CompetitorsCompetitorDetailPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompetitorsCompetitorDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/CompetitorsCompetitorDetailPagePage.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Swords, Loader2 } from 'lucide-react';\nimport { useParams } from 'react-router';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { CompetitorHeaderActions } from '../controls/CompetitorControls';\n\nconst GET_COMPETITOR = gql`\n query GetCompetitor($id: ID!) {\n planmagnetCompetitor(id: $id) {\n id\n workspaceId\n name\n website\n description\n tags\n createdBy\n createdAt\n updatedAt\n featureRequests {\n id\n title\n status\n }\n }\n }\n`;\n\ninterface FeatureRequestNode {\n id: string;\n title: string;\n status: string;\n}\n\nexport function CompetitorsCompetitorDetailPagePage() {\n const navigate = usePlanMagnetNavigate();\n const params = useParams<{ id: string }>();\n const competitorId = params.id ?? '';\n\n const { data, loading, error } = useQuery<{\n planmagnetCompetitor: {\n id: string;\n name: string;\n website: string | null;\n description: string | null;\n tags: string[];\n createdAt: string;\n updatedAt: string;\n featureRequests: FeatureRequestNode[];\n } | null;\n }>(GET_COMPETITOR, { variables: { id: competitorId }, skip: !competitorId });\n\n // Only before the first load: a refetch must not blank the page (it unmounts every component on it and\n // loses their state).\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load competitor: {error.message}</div>;\n }\n\n const competitor = data?.planmagnetCompetitor;\n if (!competitor) return <div className=\"p-6 text-muted-foreground\">Competitor not found.</div>;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <Swords className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{competitor.name}</h1>\n </div>\n {competitor.website && <p className=\"text-sm text-primary mt-1\">{competitor.website}</p>}\n </div>\n <CompetitorHeaderActions competitorId={competitor.id} name={competitor.name} />\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Notes</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm\">{competitor.description ?? 'No notes yet.'}</p>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Linked Feature Requests ({competitor.featureRequests.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {competitor.featureRequests.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n Not linked to any feature request yet. Add it from a feature request’s\n detail page.\n </p>\n )}\n {competitor.featureRequests.map((fr) => (\n <button\n key={fr.id}\n type=\"button\"\n className=\"flex w-full items-center justify-between rounded-md border p-3 text-left hover:bg-muted\"\n onClick={() => navigate(detailPath('/products/feature-detail-page', fr.id))}\n >\n <span className=\"text-sm font-medium\">{fr.title}</span>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {fr.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </button>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(competitor.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(competitor.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n {competitor.tags.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Tags</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-wrap gap-1\">\n {competitor.tags.map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AASA,IAAM,IAAiB,CAAG;;;;;;;;;;;;;;;;;;;;AA2B1B,SAAgB,IAAsC;CACpD,IAAM,IAAW,GAAuB,EAElC,IADS,GAA2B,CACd,MAAM,IAE5B,EAAE,SAAM,YAAS,aAAU,EAW9B,GAAgB;EAAE,WAAW,EAAE,IAAI,GAAc;EAAE,MAAM,CAAC;EAAc,CAAC;AAI5E,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,8CAA+C,CAAA;EAC9D,CAAA;AAIV,KAAI,EACF,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAsC,+BAA4B,EAAM,QAAc;;CAG/F,IAAM,IAAa,GAAM;AAGzB,QAFK,IAGH,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wBAAyB,CAAA,EAC3C,kBAAC,MAAD;KAAI,WAAU;eAA0B,EAAW;KAAU,CAAA,CACzD;OACL,EAAW,WAAW,kBAAC,KAAD;IAAG,WAAU;cAA6B,EAAW;IAAY,CAAA,CACpF,EAAA,CAAA,EACN,kBAAC,GAAD;IAAyB,cAAc,EAAW;IAAI,MAAM,EAAW;IAAQ,CAAA,CAC3E;MAEN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,SAAiB,CAAA,EACjB,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,KAAD;KAAG,WAAU;eAAW,EAAW,eAAe;KAAoB,CAAA,EAC1D,CAAA,CACT,EAAA,CAAA,EAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAA;KAAW;KAA0B,EAAW,gBAAgB;KAAO;KAAa,EAAA,CAAA,EACzE,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eAAvB,CACG,EAAW,gBAAgB,WAAW,KACrC,kBAAC,KAAD;MAAG,WAAU;gBAAgC;MAGzC,CAAA,EAEL,EAAW,gBAAgB,KAAK,MAC/B,kBAAC,UAAD;MAEE,MAAK;MACL,WAAU;MACV,eAAe,EAAS,EAAW,iCAAiC,EAAG,GAAG,CAAC;gBAJ7E,CAME,kBAAC,QAAD;OAAM,WAAU;iBAAuB,EAAG;OAAa,CAAA,EACvD,kBAAC,GAAD;OAAO,SAAQ;OAAU,WAAU;iBAChC,EAAG,OAAO,aAAa,CAAC,QAAQ,KAAK,IAAI;OACpC,CAAA,CACD;QATF,EAAG,GASD,CACT,CACU;OACT,EAAA,CAAA,CACH;OAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,WAAmB,CAAA,EACnB,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eAAvB,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAwB;OAAc,CAAA,EACtD,kBAAC,QAAD,EAAA,UAAO,IAAI,KAAK,EAAW,UAAU,CAAC,oBAAoB,EAAQ,CAAA,CAC9D;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAwB;OAAc,CAAA,EACtD,kBAAC,QAAD,EAAA,UAAO,IAAI,KAAK,EAAW,UAAU,CAAC,oBAAoB,EAAQ,CAAA,CAC9D;QACM;OACT,EAAA,CAAA,EAEN,EAAW,KAAK,SAAS,KACxB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,QAAgB,CAAA,EAChB,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eACpB,EAAW,KAAK,KAAK,MACpB,kBAAC,GAAD;MAAiB,SAAQ;MAAU,WAAU;gBAC1C;MACK,EAFI,EAEJ,CACR;KACU,CAAA,CACT,EAAA,CAAA,CAEL;MACF;KACF;MAvFgB,kBAAC,OAAD;EAAK,WAAU;YAA4B;EAA2B,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompetitorsCompetitorsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/CompetitorsCompetitorsPagePage.tsx"],"names":[],"mappings":"AAgDA,wBAAgB,8BAA8B,4CAyG7C"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { detailPath as n, rowLinkProps as r } from "../utils/rowNavigation.js";
|
|
3
|
+
import { useDeferredValue as i, useState as a } from "react";
|
|
4
|
+
import { Loader2 as o, Plus as s, Swords as c } from "lucide-react";
|
|
5
|
+
import { gql as l } from "@apollo/client";
|
|
6
|
+
import { useQuery as u } from "@apollo/client/react";
|
|
7
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
8
|
+
import { Badge as p, Button as m, Card as h, CardContent as g, CardHeader as _, CardTitle as v, Input as y } from "@burdenoff/fe-libs/ui";
|
|
9
|
+
//#region src/planmagnet/pages/CompetitorsCompetitorsPagePage.tsx
|
|
10
|
+
var b = l`
|
|
11
|
+
query ListCompetitors(
|
|
12
|
+
$workspaceId: ID!
|
|
13
|
+
$filter: PlanmagnetCompetitorFilter
|
|
14
|
+
$pagination: PlanmagnetPaginationInput
|
|
15
|
+
) {
|
|
16
|
+
planmagnetCompetitors(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {
|
|
17
|
+
items {
|
|
18
|
+
id
|
|
19
|
+
name
|
|
20
|
+
website
|
|
21
|
+
description
|
|
22
|
+
tags
|
|
23
|
+
updatedAt
|
|
24
|
+
featureRequests {
|
|
25
|
+
id
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
totalCount
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
function x() {
|
|
33
|
+
let l = t(), { workspaceId: x } = e(), [S, C] = a(""), w = i(S.trim()), { data: T, loading: E, error: D } = u(b, {
|
|
34
|
+
variables: {
|
|
35
|
+
workspaceId: x,
|
|
36
|
+
pagination: { first: 50 },
|
|
37
|
+
filter: w ? { search: w } : void 0
|
|
38
|
+
},
|
|
39
|
+
skip: !x
|
|
40
|
+
});
|
|
41
|
+
if (E && !T) return /* @__PURE__ */ d("div", {
|
|
42
|
+
className: "flex items-center justify-center p-12",
|
|
43
|
+
children: /* @__PURE__ */ d(o, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
44
|
+
});
|
|
45
|
+
if (D) return /* @__PURE__ */ f("div", {
|
|
46
|
+
className: "p-6 text-destructive",
|
|
47
|
+
children: ["Failed to load competitors: ", D.message]
|
|
48
|
+
});
|
|
49
|
+
let O = T?.planmagnetCompetitors.items ?? [], k = T?.planmagnetCompetitors.totalCount ?? 0;
|
|
50
|
+
return /* @__PURE__ */ f("div", {
|
|
51
|
+
className: "space-y-6 p-6",
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ f("div", {
|
|
54
|
+
className: "flex items-center justify-between",
|
|
55
|
+
children: [/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("h1", {
|
|
56
|
+
className: "text-2xl font-semibold",
|
|
57
|
+
children: "Competitors"
|
|
58
|
+
}), /* @__PURE__ */ f("p", {
|
|
59
|
+
className: "text-sm text-muted-foreground",
|
|
60
|
+
children: [
|
|
61
|
+
k,
|
|
62
|
+
" competitor",
|
|
63
|
+
k === 1 ? "" : "s",
|
|
64
|
+
" tracked across feature requests"
|
|
65
|
+
]
|
|
66
|
+
})] }), /* @__PURE__ */ f(m, {
|
|
67
|
+
size: "sm",
|
|
68
|
+
onClick: () => l("/products/create-competitor-page"),
|
|
69
|
+
children: [/* @__PURE__ */ d(s, { className: "mr-1 h-4 w-4" }), "New Competitor"]
|
|
70
|
+
})]
|
|
71
|
+
}),
|
|
72
|
+
/* @__PURE__ */ d(y, {
|
|
73
|
+
"aria-label": "Search competitors",
|
|
74
|
+
placeholder: "Search by name…",
|
|
75
|
+
value: S,
|
|
76
|
+
onChange: (e) => C(e.target.value),
|
|
77
|
+
className: "max-w-xs"
|
|
78
|
+
}),
|
|
79
|
+
/* @__PURE__ */ d("div", {
|
|
80
|
+
className: "grid grid-cols-1 md:grid-cols-2 gap-4",
|
|
81
|
+
children: O.map((e) => /* @__PURE__ */ f(h, {
|
|
82
|
+
...r(() => l(n("/products/competitor-detail-page", e.id)), e.name),
|
|
83
|
+
children: [/* @__PURE__ */ d(_, {
|
|
84
|
+
className: "pb-2",
|
|
85
|
+
children: /* @__PURE__ */ f(v, {
|
|
86
|
+
className: "text-base flex items-center gap-2",
|
|
87
|
+
children: [/* @__PURE__ */ d(c, { className: "h-4 w-4 text-primary" }), e.name]
|
|
88
|
+
})
|
|
89
|
+
}), /* @__PURE__ */ f(g, {
|
|
90
|
+
className: "space-y-3",
|
|
91
|
+
children: [
|
|
92
|
+
/* @__PURE__ */ d("p", {
|
|
93
|
+
className: "text-sm text-muted-foreground",
|
|
94
|
+
children: e.description ?? "No description"
|
|
95
|
+
}),
|
|
96
|
+
e.website && /* @__PURE__ */ d("p", {
|
|
97
|
+
className: "text-xs text-primary",
|
|
98
|
+
children: e.website
|
|
99
|
+
}),
|
|
100
|
+
/* @__PURE__ */ f("div", {
|
|
101
|
+
className: "flex items-center justify-between text-xs text-muted-foreground border-t pt-2",
|
|
102
|
+
children: [/* @__PURE__ */ f("span", { children: [
|
|
103
|
+
e.featureRequests.length,
|
|
104
|
+
" linked feature",
|
|
105
|
+
e.featureRequests.length === 1 ? "" : "s"
|
|
106
|
+
] }), e.tags.length > 0 && /* @__PURE__ */ d("div", {
|
|
107
|
+
className: "flex gap-1",
|
|
108
|
+
children: e.tags.slice(0, 3).map((e) => /* @__PURE__ */ d(p, {
|
|
109
|
+
variant: "outline",
|
|
110
|
+
className: "text-[10px]",
|
|
111
|
+
children: e
|
|
112
|
+
}, e))
|
|
113
|
+
})]
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
})]
|
|
117
|
+
}, e.id))
|
|
118
|
+
}),
|
|
119
|
+
O.length === 0 && /* @__PURE__ */ d("p", {
|
|
120
|
+
className: "text-center text-muted-foreground py-8",
|
|
121
|
+
children: S ? "No competitors match your search." : "No competitors yet. Add one to start tracking it against your feature requests."
|
|
122
|
+
})
|
|
123
|
+
]
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { x as CompetitorsCompetitorsPagePage };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=CompetitorsCompetitorsPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompetitorsCompetitorsPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/CompetitorsCompetitorsPagePage.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { Swords, Plus, Loader2 } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { rowLinkProps, detailPath } from '../utils/rowNavigation';\n\nconst LIST_COMPETITORS = gql`\n query ListCompetitors(\n $workspaceId: ID!\n $filter: PlanmagnetCompetitorFilter\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetCompetitors(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {\n items {\n id\n name\n website\n description\n tags\n updatedAt\n featureRequests {\n id\n }\n }\n totalCount\n }\n }\n`;\n\ninterface CompetitorNode {\n id: string;\n name: string;\n website: string | null;\n description: string | null;\n tags: string[];\n featureRequests: { id: string }[];\n}\n\nexport function CompetitorsCompetitorsPagePage() {\n const navigate = usePlanMagnetNavigate();\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n\n const { data, loading, error } = useQuery<{\n planmagnetCompetitors: { items: CompetitorNode[]; totalCount: number };\n }>(LIST_COMPETITORS, {\n variables: {\n workspaceId,\n pagination: { first: 50 },\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n\n // Only before the first load: a refetch must not blank the page (it unmounts every component on it and\n // loses their state).\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load competitors: {error.message}</div>;\n }\n\n const competitors = data?.planmagnetCompetitors.items ?? [];\n const totalCount = data?.planmagnetCompetitors.totalCount ?? 0;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Competitors</h1>\n <p className=\"text-sm text-muted-foreground\">\n {totalCount} competitor{totalCount === 1 ? '' : 's'} tracked across feature requests\n </p>\n </div>\n <Button size=\"sm\" onClick={() => navigate('/products/create-competitor-page')}>\n <Plus className=\"mr-1 h-4 w-4\" />\n New Competitor\n </Button>\n </div>\n\n <Input\n aria-label=\"Search competitors\"\n placeholder=\"Search by name…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"max-w-xs\"\n />\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n {competitors.map((comp) => (\n <Card\n key={comp.id}\n {...rowLinkProps(\n () => navigate(detailPath('/products/competitor-detail-page', comp.id)),\n comp.name\n )}\n >\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Swords className=\"h-4 w-4 text-primary\" />\n {comp.name}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n {comp.description ?? 'No description'}\n </p>\n {comp.website && <p className=\"text-xs text-primary\">{comp.website}</p>}\n <div className=\"flex items-center justify-between text-xs text-muted-foreground border-t pt-2\">\n <span>\n {comp.featureRequests.length} linked feature\n {comp.featureRequests.length === 1 ? '' : 's'}\n </span>\n {comp.tags.length > 0 && (\n <div className=\"flex gap-1\">\n {comp.tags.slice(0, 3).map((tag) => (\n <Badge key={tag} variant=\"outline\" className=\"text-[10px]\">\n {tag}\n </Badge>\n ))}\n </div>\n )}\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n {competitors.length === 0 && (\n <p className=\"text-center text-muted-foreground py-8\">\n {search\n ? 'No competitors match your search.'\n : 'No competitors yet. Add one to start tracking it against your feature requests.'}\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAgBA,IAAM,IAAmB,CAAG;;;;;;;;;;;;;;;;;;;;;;AAgC5B,SAAgB,IAAiC;CAC/C,IAAM,IAAW,GAAuB,EAClC,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,IAAW,EAAiB,EAAO,MAAM,CAAC,EAE1C,EAAE,SAAM,YAAS,aAAU,EAE9B,GAAkB;EACnB,WAAW;GACT;GACA,YAAY,EAAE,OAAO,IAAI;GACzB,QAAQ,IAAW,EAAE,QAAQ,GAAU,GAAG,KAAA;GAC3C;EACD,MAAM,CAAC;EACR,CAAC;AAIF,KAAI,KAAW,CAAC,EACd,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,WAAU,8CAA+C,CAAA;EAC9D,CAAA;AAIV,KAAI,EACF,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAf,CAAsC,gCAA6B,EAAM,QAAc;;CAGhG,IAAM,IAAc,GAAM,sBAAsB,SAAS,EAAE,EACrD,IAAa,GAAM,sBAAsB,cAAc;AAE7D,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eAAyB;KAAgB,CAAA,EACvD,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG;MAAW;MAAY,MAAe,IAAI,KAAK;MAAI;MAClD;OACA,EAAA,CAAA,EACN,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAS,mCAAmC;eAA7E,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,iBAE1B;OACL;;GAEN,kBAAC,GAAD;IACE,cAAW;IACX,aAAY;IACZ,OAAO;IACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;IAC1C,WAAU;IACV,CAAA;GAEF,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAY,KAAK,MAChB,kBAAC,GAAD;KAEE,GAAI,QACI,EAAS,EAAW,oCAAoC,EAAK,GAAG,CAAC,EACvE,EAAK,KACN;eALH,CAOE,kBAAC,GAAD;MAAY,WAAU;gBACpB,kBAAC,GAAD;OAAW,WAAU;iBAArB,CACE,kBAAC,GAAD,EAAQ,WAAU,wBAAyB,CAAA,EAC1C,EAAK,KACI;;MACD,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAK,eAAe;QACnB,CAAA;OACH,EAAK,WAAW,kBAAC,KAAD;QAAG,WAAU;kBAAwB,EAAK;QAAY,CAAA;OACvE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD,EAAA,UAAA;SACG,EAAK,gBAAgB;SAAO;SAC5B,EAAK,gBAAgB,WAAW,IAAI,KAAK;SACrC,EAAA,CAAA,EACN,EAAK,KAAK,SAAS,KAClB,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAK,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MAC1B,kBAAC,GAAD;UAAiB,SAAQ;UAAU,WAAU;oBAC1C;UACK,EAFI,EAEJ,CACR;SACE,CAAA,CAEJ;;OACM;QACT;OAjCA,EAAK,GAiCL,CACP;IACE,CAAA;GACL,EAAY,WAAW,KACtB,kBAAC,KAAD;IAAG,WAAU;cACV,IACG,sCACA;IACF,CAAA;GAEF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompetitorsCreateCompetitorPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/CompetitorsCreateCompetitorPagePage.tsx"],"names":[],"mappings":"AAiBA,wBAAgB,mCAAmC,4CA0HlD"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { throwOnError as t } from "../utils/mutationResult.js";
|
|
3
|
+
import { useState as n } from "react";
|
|
4
|
+
import { Loader2 as r, Save as i, Swords as a, X as o } from "lucide-react";
|
|
5
|
+
import { gql as s } from "@apollo/client";
|
|
6
|
+
import { useMutation as c } from "@apollo/client/react";
|
|
7
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
8
|
+
import { Button as d, Card as f, CardContent as p, CardHeader as m, CardTitle as h, Input as g } from "@burdenoff/fe-libs/ui";
|
|
9
|
+
//#region src/planmagnet/pages/CompetitorsCreateCompetitorPagePage.tsx
|
|
10
|
+
var _ = s`
|
|
11
|
+
mutation CreateCompetitor($input: PlanmagnetCreateCompetitorInput!) {
|
|
12
|
+
planmagnetCreateCompetitor(input: $input) {
|
|
13
|
+
id
|
|
14
|
+
name
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
function v() {
|
|
19
|
+
let s = e(), [v, y] = n({
|
|
20
|
+
name: "",
|
|
21
|
+
website: "",
|
|
22
|
+
description: "",
|
|
23
|
+
tags: ""
|
|
24
|
+
}), [b, x] = n(null), [S, { loading: C }] = c(_, { refetchQueries: ["ListCompetitors", "PickerCompetitors"] });
|
|
25
|
+
return /* @__PURE__ */ u("div", {
|
|
26
|
+
className: "space-y-6 p-6 max-w-4xl",
|
|
27
|
+
children: [
|
|
28
|
+
/* @__PURE__ */ u("div", {
|
|
29
|
+
className: "flex items-center justify-between",
|
|
30
|
+
children: [/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("h1", {
|
|
31
|
+
className: "text-2xl font-semibold",
|
|
32
|
+
children: "Add Competitor"
|
|
33
|
+
}), /* @__PURE__ */ l("p", {
|
|
34
|
+
className: "text-sm text-muted-foreground",
|
|
35
|
+
children: "Track a competitor so you can link it to feature requests it offers."
|
|
36
|
+
})] }), /* @__PURE__ */ u("div", {
|
|
37
|
+
className: "flex gap-2",
|
|
38
|
+
children: [/* @__PURE__ */ u(d, {
|
|
39
|
+
variant: "outline",
|
|
40
|
+
size: "sm",
|
|
41
|
+
onClick: () => s("/products/competitors-page"),
|
|
42
|
+
children: [/* @__PURE__ */ l(o, { className: "mr-1 h-4 w-4" }), "Cancel"]
|
|
43
|
+
}), /* @__PURE__ */ u(d, {
|
|
44
|
+
size: "sm",
|
|
45
|
+
onClick: async () => {
|
|
46
|
+
if (v.name.trim()) {
|
|
47
|
+
x(null);
|
|
48
|
+
try {
|
|
49
|
+
await t(S({ variables: { input: {
|
|
50
|
+
name: v.name.trim(),
|
|
51
|
+
website: v.website.trim() || void 0,
|
|
52
|
+
description: v.description.trim() || void 0,
|
|
53
|
+
tags: v.tags.split(",").map((e) => e.trim()).filter(Boolean)
|
|
54
|
+
} } })), s("/products/competitors-page");
|
|
55
|
+
} catch (e) {
|
|
56
|
+
x(e instanceof Error ? e.message : "The competitor could not be created.");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
disabled: C || !v.name.trim(),
|
|
61
|
+
children: [C ? /* @__PURE__ */ l(r, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ l(i, { className: "mr-1 h-4 w-4" }), "Add Competitor"]
|
|
62
|
+
})]
|
|
63
|
+
})]
|
|
64
|
+
}),
|
|
65
|
+
b && /* @__PURE__ */ u("div", {
|
|
66
|
+
role: "alert",
|
|
67
|
+
className: "p-3 rounded-md bg-destructive/10 text-destructive text-sm",
|
|
68
|
+
children: ["Failed to add competitor: ", b]
|
|
69
|
+
}),
|
|
70
|
+
/* @__PURE__ */ u(f, { children: [/* @__PURE__ */ l(m, { children: /* @__PURE__ */ u(h, {
|
|
71
|
+
className: "flex items-center gap-2",
|
|
72
|
+
children: [/* @__PURE__ */ l(a, { className: "h-4 w-4" }), "Competitor Details"]
|
|
73
|
+
}) }), /* @__PURE__ */ u(p, {
|
|
74
|
+
className: "space-y-4",
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("label", {
|
|
77
|
+
className: "text-sm font-medium",
|
|
78
|
+
children: "Name"
|
|
79
|
+
}), /* @__PURE__ */ l(g, {
|
|
80
|
+
value: v.name,
|
|
81
|
+
onChange: (e) => y({
|
|
82
|
+
...v,
|
|
83
|
+
name: e.target.value
|
|
84
|
+
}),
|
|
85
|
+
className: "mt-1",
|
|
86
|
+
placeholder: "e.g. Linear"
|
|
87
|
+
})] }),
|
|
88
|
+
/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("label", {
|
|
89
|
+
className: "text-sm font-medium",
|
|
90
|
+
children: "Website"
|
|
91
|
+
}), /* @__PURE__ */ l(g, {
|
|
92
|
+
value: v.website,
|
|
93
|
+
onChange: (e) => y({
|
|
94
|
+
...v,
|
|
95
|
+
website: e.target.value
|
|
96
|
+
}),
|
|
97
|
+
className: "mt-1",
|
|
98
|
+
placeholder: "https://…"
|
|
99
|
+
})] }),
|
|
100
|
+
/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("label", {
|
|
101
|
+
className: "text-sm font-medium",
|
|
102
|
+
children: "Description / Notes"
|
|
103
|
+
}), /* @__PURE__ */ l("textarea", {
|
|
104
|
+
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]",
|
|
105
|
+
value: v.description,
|
|
106
|
+
onChange: (e) => y({
|
|
107
|
+
...v,
|
|
108
|
+
description: e.target.value
|
|
109
|
+
}),
|
|
110
|
+
placeholder: "What they offer, how they compare…"
|
|
111
|
+
})] }),
|
|
112
|
+
/* @__PURE__ */ u("div", { children: [/* @__PURE__ */ l("label", {
|
|
113
|
+
className: "text-sm font-medium",
|
|
114
|
+
children: "Tags (comma-separated)"
|
|
115
|
+
}), /* @__PURE__ */ l(g, {
|
|
116
|
+
value: v.tags,
|
|
117
|
+
onChange: (e) => y({
|
|
118
|
+
...v,
|
|
119
|
+
tags: e.target.value
|
|
120
|
+
}),
|
|
121
|
+
className: "mt-1",
|
|
122
|
+
placeholder: "e.g. enterprise, pm-tool"
|
|
123
|
+
})] })
|
|
124
|
+
]
|
|
125
|
+
})] })
|
|
126
|
+
]
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { v as CompetitorsCreateCompetitorPagePage };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=CompetitorsCreateCompetitorPagePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompetitorsCreateCompetitorPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/CompetitorsCreateCompetitorPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Save, X, Swords, Loader2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\n\nconst CREATE_COMPETITOR = gql`\n mutation CreateCompetitor($input: PlanmagnetCreateCompetitorInput!) {\n planmagnetCreateCompetitor(input: $input) {\n id\n name\n }\n }\n`;\n\nexport function CompetitorsCreateCompetitorPagePage() {\n const navigate = usePlanMagnetNavigate();\n\n const [form, setForm] = useState({\n name: '',\n website: '',\n description: '',\n tags: '',\n });\n const [saveError, setSaveError] = useState<string | null>(null);\n const [createCompetitor, { loading: saving }] = useMutation(CREATE_COMPETITOR, {\n refetchQueries: ['ListCompetitors', 'PickerCompetitors'],\n });\n\n const handleCreate = async () => {\n if (!form.name.trim()) return;\n setSaveError(null);\n try {\n await throwOnError(\n createCompetitor({\n variables: {\n input: {\n name: form.name.trim(),\n website: form.website.trim() || undefined,\n description: form.description.trim() || undefined,\n tags: form.tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean),\n },\n },\n })\n );\n navigate('/products/competitors-page');\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'The competitor could not be created.');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Add Competitor</h1>\n <p className=\"text-sm text-muted-foreground\">\n Track a competitor so you can link it to feature requests it offers.\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => navigate('/products/competitors-page')}\n >\n <X className=\"mr-1 h-4 w-4\" />\n Cancel\n </Button>\n <Button size=\"sm\" onClick={handleCreate} disabled={saving || !form.name.trim()}>\n {saving ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"mr-1 h-4 w-4\" />\n )}\n Add Competitor\n </Button>\n </div>\n </div>\n\n {saveError && (\n <div role=\"alert\" className=\"p-3 rounded-md bg-destructive/10 text-destructive text-sm\">\n Failed to add competitor: {saveError}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Swords className=\"h-4 w-4\" />\n Competitor Details\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium\">Name</label>\n <Input\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className=\"mt-1\"\n placeholder=\"e.g. Linear\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Website</label>\n <Input\n value={form.website}\n onChange={(e) => setForm({ ...form, website: e.target.value })}\n className=\"mt-1\"\n placeholder=\"https://…\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Description / Notes</label>\n <textarea\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"What they offer, how they compare…\"\n />\n </div>\n <div>\n <label className=\"text-sm font-medium\">Tags (comma-separated)</label>\n <Input\n value={form.tags}\n onChange={(e) => setForm({ ...form, tags: e.target.value })}\n className=\"mt-1\"\n placeholder=\"e.g. enterprise, pm-tool\"\n />\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompetitorsEditCompetitorPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/CompetitorsEditCompetitorPagePage.tsx"],"names":[],"mappings":"AAmCA,wBAAgB,iCAAiC,4CA8KhD"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { throwOnError as t } from "../utils/mutationResult.js";
|
|
3
|
+
import { detailPath as n } from "../utils/rowNavigation.js";
|
|
4
|
+
import { useMemo as r, useState as i } from "react";
|
|
5
|
+
import { ArrowLeft as a, Loader2 as o, Save as s } from "lucide-react";
|
|
6
|
+
import { useParams as c } from "react-router";
|
|
7
|
+
import { gql as l } from "@apollo/client";
|
|
8
|
+
import { useMutation as u, useQuery as d } from "@apollo/client/react";
|
|
9
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
10
|
+
import { Button as m, Card as h, CardContent as g, CardHeader as _, CardTitle as v, Input as y } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
//#region src/planmagnet/pages/CompetitorsEditCompetitorPagePage.tsx
|
|
12
|
+
var b = l`
|
|
13
|
+
query GetCompetitorForEdit($id: ID!) {
|
|
14
|
+
planmagnetCompetitor(id: $id) {
|
|
15
|
+
id
|
|
16
|
+
name
|
|
17
|
+
website
|
|
18
|
+
description
|
|
19
|
+
tags
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
`, x = l`
|
|
23
|
+
mutation UpdateCompetitor($id: ID!, $input: PlanmagnetUpdateCompetitorInput!) {
|
|
24
|
+
planmagnetUpdateCompetitor(id: $id, input: $input) {
|
|
25
|
+
id
|
|
26
|
+
name
|
|
27
|
+
website
|
|
28
|
+
description
|
|
29
|
+
tags
|
|
30
|
+
updatedAt
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
function S() {
|
|
35
|
+
let l = e(), S = c().id ?? "", { data: C, loading: w, error: T } = d(b, {
|
|
36
|
+
variables: { id: S },
|
|
37
|
+
skip: !S
|
|
38
|
+
}), [E, { loading: D }] = u(x, {
|
|
39
|
+
refetchQueries: [
|
|
40
|
+
"ListCompetitors",
|
|
41
|
+
"GetCompetitorForEdit",
|
|
42
|
+
"GetCompetitor",
|
|
43
|
+
"PickerCompetitors"
|
|
44
|
+
],
|
|
45
|
+
awaitRefetchQueries: !0
|
|
46
|
+
}), [O, k] = i(null), [A, j] = i(!1), M = C?.planmagnetCompetitor, N = r(() => ({
|
|
47
|
+
name: M?.name ?? "",
|
|
48
|
+
website: M?.website ?? "",
|
|
49
|
+
description: M?.description ?? "",
|
|
50
|
+
tags: M?.tags?.join(", ") ?? ""
|
|
51
|
+
}), [M]), [P, F] = i({}), I = {
|
|
52
|
+
...N,
|
|
53
|
+
...P
|
|
54
|
+
}, L = (e) => F(e);
|
|
55
|
+
return w && !C ? /* @__PURE__ */ f("div", {
|
|
56
|
+
className: "flex items-center justify-center p-12",
|
|
57
|
+
children: /* @__PURE__ */ f(o, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
58
|
+
}) : T ? /* @__PURE__ */ p("div", {
|
|
59
|
+
className: "p-6 text-destructive",
|
|
60
|
+
children: ["Failed to load competitor: ", T.message]
|
|
61
|
+
}) : M ? /* @__PURE__ */ p("div", {
|
|
62
|
+
className: "space-y-6 p-6 max-w-4xl",
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ p("div", {
|
|
65
|
+
className: "flex items-center justify-between",
|
|
66
|
+
children: [/* @__PURE__ */ p("div", {
|
|
67
|
+
className: "flex items-center gap-3",
|
|
68
|
+
children: [/* @__PURE__ */ f(m, {
|
|
69
|
+
variant: "ghost",
|
|
70
|
+
size: "sm",
|
|
71
|
+
"aria-label": "Back to the competitor",
|
|
72
|
+
onClick: () => l(n("/products/competitor-detail-page", S)),
|
|
73
|
+
children: /* @__PURE__ */ f(a, { className: "h-4 w-4" })
|
|
74
|
+
}), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h1", {
|
|
75
|
+
className: "text-2xl font-semibold",
|
|
76
|
+
children: "Edit Competitor"
|
|
77
|
+
}), /* @__PURE__ */ f("p", {
|
|
78
|
+
className: "text-sm text-muted-foreground",
|
|
79
|
+
children: M.name
|
|
80
|
+
})] })]
|
|
81
|
+
}), /* @__PURE__ */ p(m, {
|
|
82
|
+
size: "sm",
|
|
83
|
+
onClick: async () => {
|
|
84
|
+
if (!M || !I.name.trim()) return;
|
|
85
|
+
let e = (e) => e.split(",").map((e) => e.trim()).filter(Boolean), n = {
|
|
86
|
+
name: I.name.trim(),
|
|
87
|
+
website: I.website.trim() || null,
|
|
88
|
+
description: I.description.trim() || null,
|
|
89
|
+
tags: e(I.tags)
|
|
90
|
+
}, r = {
|
|
91
|
+
name: N.name,
|
|
92
|
+
website: N.website.trim() || null,
|
|
93
|
+
description: N.description.trim() || null,
|
|
94
|
+
tags: e(N.tags)
|
|
95
|
+
}, i = Object.fromEntries(Object.entries(n).filter(([e, t]) => JSON.stringify(t) !== JSON.stringify(r[e])));
|
|
96
|
+
if (k(null), j(!1), Object.keys(i).length === 0) {
|
|
97
|
+
j(!0);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
await t(E({ variables: {
|
|
102
|
+
id: S,
|
|
103
|
+
input: i
|
|
104
|
+
} })), j(!0);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
k(e instanceof Error ? e.message : "The competitor could not be saved.");
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
disabled: D || !I.name.trim(),
|
|
110
|
+
children: [D ? /* @__PURE__ */ f(o, { className: "mr-1 h-4 w-4 animate-spin" }) : /* @__PURE__ */ f(s, { className: "mr-1 h-4 w-4" }), "Save Changes"]
|
|
111
|
+
})]
|
|
112
|
+
}),
|
|
113
|
+
A && /* @__PURE__ */ f("div", {
|
|
114
|
+
role: "status",
|
|
115
|
+
className: "p-3 bg-muted text-status-success-text rounded-md text-sm",
|
|
116
|
+
children: "Changes saved."
|
|
117
|
+
}),
|
|
118
|
+
O && /* @__PURE__ */ f("div", {
|
|
119
|
+
role: "alert",
|
|
120
|
+
className: "p-3 rounded-md bg-destructive/10 text-destructive text-sm",
|
|
121
|
+
children: O
|
|
122
|
+
}),
|
|
123
|
+
/* @__PURE__ */ p(h, { children: [/* @__PURE__ */ f(_, { children: /* @__PURE__ */ f(v, { children: "Competitor Details" }) }), /* @__PURE__ */ p(g, {
|
|
124
|
+
className: "space-y-4",
|
|
125
|
+
children: [
|
|
126
|
+
/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
|
|
127
|
+
className: "text-sm font-medium",
|
|
128
|
+
children: "Name"
|
|
129
|
+
}), /* @__PURE__ */ f(y, {
|
|
130
|
+
value: I.name,
|
|
131
|
+
onChange: (e) => L({
|
|
132
|
+
...I,
|
|
133
|
+
name: e.target.value
|
|
134
|
+
}),
|
|
135
|
+
className: "mt-1"
|
|
136
|
+
})] }),
|
|
137
|
+
/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
|
|
138
|
+
className: "text-sm font-medium",
|
|
139
|
+
children: "Website"
|
|
140
|
+
}), /* @__PURE__ */ f(y, {
|
|
141
|
+
value: I.website,
|
|
142
|
+
onChange: (e) => L({
|
|
143
|
+
...I,
|
|
144
|
+
website: e.target.value
|
|
145
|
+
}),
|
|
146
|
+
className: "mt-1"
|
|
147
|
+
})] }),
|
|
148
|
+
/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
|
|
149
|
+
className: "text-sm font-medium",
|
|
150
|
+
children: "Description / Notes"
|
|
151
|
+
}), /* @__PURE__ */ f("textarea", {
|
|
152
|
+
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[80px]",
|
|
153
|
+
value: I.description,
|
|
154
|
+
onChange: (e) => L({
|
|
155
|
+
...I,
|
|
156
|
+
description: e.target.value
|
|
157
|
+
})
|
|
158
|
+
})] }),
|
|
159
|
+
/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("label", {
|
|
160
|
+
className: "text-sm font-medium",
|
|
161
|
+
children: "Tags (comma-separated)"
|
|
162
|
+
}), /* @__PURE__ */ f(y, {
|
|
163
|
+
value: I.tags,
|
|
164
|
+
onChange: (e) => L({
|
|
165
|
+
...I,
|
|
166
|
+
tags: e.target.value
|
|
167
|
+
}),
|
|
168
|
+
className: "mt-1"
|
|
169
|
+
})] })
|
|
170
|
+
]
|
|
171
|
+
})] })
|
|
172
|
+
]
|
|
173
|
+
}) : /* @__PURE__ */ f("div", {
|
|
174
|
+
className: "p-6 text-muted-foreground",
|
|
175
|
+
children: "Competitor not found."
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
export { S as CompetitorsEditCompetitorPagePage };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=CompetitorsEditCompetitorPagePage.js.map
|