@burdenoff/microfe-planmagnet 2026.928.4 → 2026.928.6
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 +130 -97
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +256 -156
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts +5 -0
- package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts.map +1 -0
- package/dist/planmagnet/automation/AutomationExecutionsPanel.js +107 -0
- package/dist/planmagnet/automation/AutomationExecutionsPanel.js.map +1 -0
- package/dist/planmagnet/automation/AutomationRuleFields.d.ts +29 -0
- package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -0
- package/dist/planmagnet/automation/AutomationRuleFields.js +308 -0
- package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -0
- package/dist/planmagnet/automation/automationRuleTypes.d.ts +60 -0
- package/dist/planmagnet/automation/automationRuleTypes.d.ts.map +1 -0
- package/dist/planmagnet/automation/automationRuleTypes.js +113 -0
- package/dist/planmagnet/automation/automationRuleTypes.js.map +1 -0
- package/dist/planmagnet/board/BulkActions.d.ts +11 -0
- package/dist/planmagnet/board/BulkActions.d.ts.map +1 -0
- package/dist/planmagnet/board/BulkActions.js +182 -0
- package/dist/planmagnet/board/BulkActions.js.map +1 -0
- package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
- package/dist/planmagnet/board/ProjectBoard.js +289 -254
- package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
- package/dist/planmagnet/budgets/BudgetControls.d.ts +30 -0
- package/dist/planmagnet/budgets/BudgetControls.d.ts.map +1 -0
- package/dist/planmagnet/budgets/BudgetControls.js +470 -0
- package/dist/planmagnet/budgets/BudgetControls.js.map +1 -0
- package/dist/planmagnet/budgets/ExpenseControls.d.ts +27 -0
- package/dist/planmagnet/budgets/ExpenseControls.d.ts.map +1 -0
- package/dist/planmagnet/budgets/ExpenseControls.js +366 -0
- package/dist/planmagnet/budgets/ExpenseControls.js.map +1 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts +5 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts.map +1 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.js +112 -0
- package/dist/planmagnet/budgets/ProjectBudgetsSummary.js.map +1 -0
- package/dist/planmagnet/budgets/budgetMath.d.ts +26 -0
- package/dist/planmagnet/budgets/budgetMath.d.ts.map +1 -0
- package/dist/planmagnet/budgets/budgetMath.js +49 -0
- package/dist/planmagnet/budgets/budgetMath.js.map +1 -0
- package/dist/planmagnet/controls/AutomationRuleControls.d.ts +23 -0
- package/dist/planmagnet/controls/AutomationRuleControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/AutomationRuleControls.js +172 -0
- package/dist/planmagnet/controls/AutomationRuleControls.js.map +1 -0
- package/dist/planmagnet/controls/CompetitorControls.js +15 -15
- package/dist/planmagnet/controls/CustomFieldControls.d.ts +42 -0
- package/dist/planmagnet/controls/CustomFieldControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/CustomFieldControls.js +84 -0
- package/dist/planmagnet/controls/CustomFieldControls.js.map +1 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts +8 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts.map +1 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.js +315 -0
- package/dist/planmagnet/controls/CustomFieldValuesPanel.js.map +1 -0
- package/dist/planmagnet/controls/FeatureControls.js +54 -54
- package/dist/planmagnet/controls/ObjectiveControls.js +15 -15
- package/dist/planmagnet/controls/Pickers.d.ts +7 -0
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +58 -20
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/controls/PortfolioControls.js +7 -7
- package/dist/planmagnet/controls/ProgramControls.js +19 -19
- package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/ProjectControls.js +54 -47
- package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
- package/dist/planmagnet/controls/ReleaseControls.js +7 -7
- package/dist/planmagnet/controls/RiskControls.d.ts +27 -0
- package/dist/planmagnet/controls/RiskControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/RiskControls.js +346 -0
- package/dist/planmagnet/controls/RiskControls.js.map +1 -0
- package/dist/planmagnet/controls/TemplateControls.d.ts +15 -0
- package/dist/planmagnet/controls/TemplateControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/TemplateControls.js +55 -0
- package/dist/planmagnet/controls/TemplateControls.js.map +1 -0
- package/dist/planmagnet/controls/WorkItemControls.d.ts +6 -2
- package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/WorkItemControls.js +9 -9
- package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
- package/dist/planmagnet/create/CreateDialogs.d.ts +20 -0
- package/dist/planmagnet/create/CreateDialogs.d.ts.map +1 -1
- package/dist/planmagnet/create/CreateDialogs.js +455 -116
- package/dist/planmagnet/create/CreateDialogs.js.map +1 -1
- package/dist/planmagnet/dependency/DependencyPanel.d.ts +6 -0
- package/dist/planmagnet/dependency/DependencyPanel.d.ts.map +1 -0
- package/dist/planmagnet/dependency/DependencyPanel.js +310 -0
- package/dist/planmagnet/dependency/DependencyPanel.js.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +171 -0
- package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js +232 -0
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js.map +1 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts +2 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +93 -0
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js.map +1 -0
- package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js +11 -3
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js +5 -5
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +9 -9
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js +6 -6
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js +4 -4
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js +6 -6
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.js +5 -5
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js +315 -0
- package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +186 -0
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +164 -0
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js +348 -0
- package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +10 -10
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +20 -10
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +13 -8
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +76 -56
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +372 -203
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +81 -72
- package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.js +278 -0
- package/dist/planmagnet/pages/RisksRiskDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js +191 -0
- package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js.map +1 -0
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +168 -122
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +5 -5
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js +4 -4
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +161 -137
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.js +124 -113
- package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js +263 -0
- package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js +254 -0
- package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.js +165 -0
- package/dist/planmagnet/pages/TasksTimesheetsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TeamCreateTeamPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamCreateTeamPagePage.js +288 -91
- package/dist/planmagnet/pages/TeamCreateTeamPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +217 -0
- package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TeamTeamEditPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TeamTeamEditPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TeamTeamEditPagePage.js +211 -0
- package/dist/planmagnet/pages/TeamTeamEditPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TeamTeamsListPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TeamTeamsListPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TeamTeamsListPagePage.js +118 -0
- package/dist/planmagnet/pages/TeamTeamsListPagePage.js.map +1 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js +316 -0
- package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js.map +1 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +208 -0
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -0
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts +0 -5
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +47 -89
- package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.js +24 -24
- package/dist/planmagnet/sprints/SprintControls.d.ts +15 -3
- package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.js +293 -190
- package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
- package/dist/planmagnet/team/TeamControls.d.ts +31 -0
- package/dist/planmagnet/team/TeamControls.d.ts.map +1 -0
- package/dist/planmagnet/team/TeamControls.js +407 -0
- package/dist/planmagnet/team/TeamControls.js.map +1 -0
- package/dist/planmagnet/time/TaskTimePanel.d.ts +7 -0
- package/dist/planmagnet/time/TaskTimePanel.d.ts.map +1 -0
- package/dist/planmagnet/time/TaskTimePanel.js +104 -0
- package/dist/planmagnet/time/TaskTimePanel.js.map +1 -0
- package/dist/planmagnet/time/TimeEntryControls.d.ts +37 -0
- package/dist/planmagnet/time/TimeEntryControls.d.ts.map +1 -0
- package/dist/planmagnet/time/TimeEntryControls.js +338 -0
- package/dist/planmagnet/time/TimeEntryControls.js.map +1 -0
- package/dist/planmagnet/time/TimesheetControls.d.ts +18 -0
- package/dist/planmagnet/time/TimesheetControls.d.ts.map +1 -0
- package/dist/planmagnet/time/TimesheetControls.js +244 -0
- package/dist/planmagnet/time/TimesheetControls.js.map +1 -0
- package/dist/planmagnet/time/timeShared.d.ts +29 -0
- package/dist/planmagnet/time/timeShared.d.ts.map +1 -0
- package/dist/planmagnet/time/timeShared.js +34 -0
- package/dist/planmagnet/time/timeShared.js.map +1 -0
- package/dist/planmagnet/utils/rowNavigation.js +5 -2
- package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { PersonName as n } from "../people/People.js";
|
|
3
|
+
import { throwOnError as r } from "../utils/mutationResult.js";
|
|
4
|
+
import { ErrorLine as i } from "./ConfirmButton.js";
|
|
5
|
+
import { useAssigneeOptions as a } from "./WorkItemControls.js";
|
|
6
|
+
import { detailPath as o } from "../utils/rowNavigation.js";
|
|
7
|
+
import { CUSTOM_FIELD_QUERIES as s, PROJECT_CUSTOM_FIELD_DEFINITIONS as c, REMOVE_CUSTOM_FIELD_VALUE as l, SET_CUSTOM_FIELD_VALUE as u, optionStrings as d } from "./CustomFieldControls.js";
|
|
8
|
+
import { useState as f } from "react";
|
|
9
|
+
import { ListPlus as p } from "lucide-react";
|
|
10
|
+
import { useMutation as m, useQuery as h } from "@apollo/client/react";
|
|
11
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
12
|
+
import { Button as v, Card as y, CardContent as b, CardHeader as x, CardTitle as S, Input as C } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
//#region src/planmagnet/controls/CustomFieldValuesPanel.tsx
|
|
14
|
+
var w = {
|
|
15
|
+
refetchQueries: s,
|
|
16
|
+
awaitRefetchQueries: !0
|
|
17
|
+
};
|
|
18
|
+
function T(e) {
|
|
19
|
+
let [t, n] = m(u, w), [i, a] = m(l, w), [o, s] = f(null);
|
|
20
|
+
return {
|
|
21
|
+
save: async (n, i) => {
|
|
22
|
+
s(null);
|
|
23
|
+
try {
|
|
24
|
+
return await r(t({ variables: { input: {
|
|
25
|
+
definitionId: n,
|
|
26
|
+
workItemId: e,
|
|
27
|
+
value: i
|
|
28
|
+
} } })), !0;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return s(e instanceof Error ? e.message : "The value could not be saved."), !1;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
clear: async (t) => {
|
|
34
|
+
s(null);
|
|
35
|
+
try {
|
|
36
|
+
return await r(i({ variables: {
|
|
37
|
+
definitionId: t,
|
|
38
|
+
workItemId: e
|
|
39
|
+
} })), !0;
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return s(e instanceof Error ? e.message : "The value could not be cleared."), !1;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
saving: n.loading || a.loading,
|
|
45
|
+
error: o
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
var E = "w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60";
|
|
49
|
+
function D({ workItemId: e, definition: t, value: n }) {
|
|
50
|
+
let { save: r, clear: a, saving: o, error: s } = T(e), c = n !== void 0, l = `custom-field-value-${t.id}`;
|
|
51
|
+
return /* @__PURE__ */ _("div", {
|
|
52
|
+
className: "space-y-1.5 border-b pb-3 last:border-0 last:pb-0",
|
|
53
|
+
"data-testid": l,
|
|
54
|
+
children: [
|
|
55
|
+
/* @__PURE__ */ _("div", {
|
|
56
|
+
className: "flex items-center justify-between gap-2",
|
|
57
|
+
children: [/* @__PURE__ */ _("label", {
|
|
58
|
+
htmlFor: l,
|
|
59
|
+
className: "text-sm font-medium",
|
|
60
|
+
children: [t.name, t.isRequired && /* @__PURE__ */ g("span", {
|
|
61
|
+
className: "text-destructive",
|
|
62
|
+
children: " *"
|
|
63
|
+
})]
|
|
64
|
+
}), c && /* @__PURE__ */ g("button", {
|
|
65
|
+
type: "button",
|
|
66
|
+
className: "text-xs text-muted-foreground hover:underline disabled:opacity-60",
|
|
67
|
+
disabled: o,
|
|
68
|
+
onClick: () => void a(t.id),
|
|
69
|
+
children: "Clear"
|
|
70
|
+
})]
|
|
71
|
+
}),
|
|
72
|
+
t.description && /* @__PURE__ */ g("p", {
|
|
73
|
+
className: "text-xs text-muted-foreground",
|
|
74
|
+
children: t.description
|
|
75
|
+
}),
|
|
76
|
+
/* @__PURE__ */ g(O, {
|
|
77
|
+
id: l,
|
|
78
|
+
definition: t,
|
|
79
|
+
value: n?.value,
|
|
80
|
+
saving: o,
|
|
81
|
+
onSave: (e) => e == null ? c ? a(t.id) : Promise.resolve(!0) : r(t.id, e)
|
|
82
|
+
}),
|
|
83
|
+
!c && /* @__PURE__ */ g("p", {
|
|
84
|
+
className: "text-xs text-muted-foreground",
|
|
85
|
+
children: "Not set."
|
|
86
|
+
}),
|
|
87
|
+
s && /* @__PURE__ */ g(i, { message: s })
|
|
88
|
+
]
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function O({ id: e, definition: t, value: n, saving: r, onSave: i }) {
|
|
92
|
+
let a = t.fieldType;
|
|
93
|
+
if (a === "BOOLEAN") {
|
|
94
|
+
let t = n === !0;
|
|
95
|
+
return /* @__PURE__ */ _("label", {
|
|
96
|
+
className: "flex items-center gap-2 text-sm",
|
|
97
|
+
children: [/* @__PURE__ */ g("input", {
|
|
98
|
+
id: e,
|
|
99
|
+
type: "checkbox",
|
|
100
|
+
checked: t,
|
|
101
|
+
disabled: r,
|
|
102
|
+
onChange: (e) => void i(e.target.checked)
|
|
103
|
+
}), t ? "Yes" : "No"]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (a === "SELECT") {
|
|
107
|
+
let a = d(t.options), o = typeof n == "string" ? n : "", s = o && !a.includes(o) ? [o, ...a] : a;
|
|
108
|
+
return /* @__PURE__ */ _("select", {
|
|
109
|
+
id: e,
|
|
110
|
+
className: E,
|
|
111
|
+
value: o,
|
|
112
|
+
disabled: r,
|
|
113
|
+
onChange: (e) => {
|
|
114
|
+
e.target.value && i(e.target.value);
|
|
115
|
+
},
|
|
116
|
+
children: [/* @__PURE__ */ g("option", {
|
|
117
|
+
value: "",
|
|
118
|
+
children: "Choose…"
|
|
119
|
+
}), s.map((e) => /* @__PURE__ */ g("option", {
|
|
120
|
+
value: e,
|
|
121
|
+
children: e
|
|
122
|
+
}, e))]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (a === "MULTI_SELECT") {
|
|
126
|
+
let e = d(t.options), a = Array.isArray(n) ? n.filter((e) => typeof e == "string") : [], o = [...new Set([...e, ...a])], s = (e) => {
|
|
127
|
+
i(a.includes(e) ? a.filter((t) => t !== e) : [...a, e]);
|
|
128
|
+
};
|
|
129
|
+
return /* @__PURE__ */ _("div", {
|
|
130
|
+
className: "flex flex-wrap gap-x-4 gap-y-1",
|
|
131
|
+
children: [o.map((e) => /* @__PURE__ */ _("label", {
|
|
132
|
+
className: "flex items-center gap-1.5 text-sm",
|
|
133
|
+
children: [/* @__PURE__ */ g("input", {
|
|
134
|
+
type: "checkbox",
|
|
135
|
+
checked: a.includes(e),
|
|
136
|
+
disabled: r,
|
|
137
|
+
onChange: () => s(e)
|
|
138
|
+
}), e]
|
|
139
|
+
}, e)), o.length === 0 && /* @__PURE__ */ g("span", {
|
|
140
|
+
className: "text-sm text-muted-foreground",
|
|
141
|
+
children: "No options configured."
|
|
142
|
+
})]
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return a === "USER" ? /* @__PURE__ */ g(k, {
|
|
146
|
+
id: e,
|
|
147
|
+
value: typeof n == "string" ? n : "",
|
|
148
|
+
disabled: r,
|
|
149
|
+
onSave: i
|
|
150
|
+
}) : a === "DATE" ? /* @__PURE__ */ g(C, {
|
|
151
|
+
id: e,
|
|
152
|
+
type: "date",
|
|
153
|
+
disabled: r,
|
|
154
|
+
value: typeof n == "string" ? n.slice(0, 10) : "",
|
|
155
|
+
onChange: (e) => void i(e.target.value || null)
|
|
156
|
+
}) : a === "NUMBER" ? /* @__PURE__ */ g(A, {
|
|
157
|
+
id: e,
|
|
158
|
+
inputType: "number",
|
|
159
|
+
initial: typeof n == "number" ? String(n) : "",
|
|
160
|
+
saving: r,
|
|
161
|
+
onSave: (e) => {
|
|
162
|
+
let t = e.trim();
|
|
163
|
+
if (t === "") return i(null);
|
|
164
|
+
let n = Number(t);
|
|
165
|
+
return Number.isFinite(n) ? i(n) : Promise.resolve(!1);
|
|
166
|
+
},
|
|
167
|
+
invalid: (e) => e.trim() !== "" && !Number.isFinite(Number(e.trim())),
|
|
168
|
+
invalidMessage: "Enter a number."
|
|
169
|
+
}) : a === "JSON" ? /* @__PURE__ */ g(A, {
|
|
170
|
+
id: e,
|
|
171
|
+
multiline: !0,
|
|
172
|
+
initial: n === void 0 ? "" : JSON.stringify(n, null, 2),
|
|
173
|
+
saving: r,
|
|
174
|
+
onSave: (e) => {
|
|
175
|
+
let t = e.trim();
|
|
176
|
+
if (t === "") return i(null);
|
|
177
|
+
try {
|
|
178
|
+
return i(JSON.parse(t));
|
|
179
|
+
} catch {
|
|
180
|
+
return Promise.resolve(!1);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
invalid: (e) => {
|
|
184
|
+
let t = e.trim();
|
|
185
|
+
if (t === "") return !1;
|
|
186
|
+
try {
|
|
187
|
+
return JSON.parse(t), !1;
|
|
188
|
+
} catch {
|
|
189
|
+
return !0;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
invalidMessage: "That is not valid JSON."
|
|
193
|
+
}) : /* @__PURE__ */ g(A, {
|
|
194
|
+
id: e,
|
|
195
|
+
inputType: a === "URL" ? "url" : a === "EMAIL" ? "email" : "text",
|
|
196
|
+
initial: typeof n == "string" ? n : "",
|
|
197
|
+
saving: r,
|
|
198
|
+
onSave: (e) => i(e.trim() === "" ? null : e.trim()),
|
|
199
|
+
invalid: () => !1,
|
|
200
|
+
invalidMessage: ""
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function k({ id: e, value: t, disabled: r, onSave: i }) {
|
|
204
|
+
let { options: o, loading: s } = a(t || null);
|
|
205
|
+
return /* @__PURE__ */ _("div", {
|
|
206
|
+
className: "flex flex-col gap-1",
|
|
207
|
+
children: [
|
|
208
|
+
/* @__PURE__ */ _("select", {
|
|
209
|
+
id: e,
|
|
210
|
+
className: E,
|
|
211
|
+
value: t,
|
|
212
|
+
disabled: r,
|
|
213
|
+
onChange: (e) => {
|
|
214
|
+
e.target.value && i(e.target.value);
|
|
215
|
+
},
|
|
216
|
+
children: [/* @__PURE__ */ g("option", {
|
|
217
|
+
value: "",
|
|
218
|
+
children: "Choose a member…"
|
|
219
|
+
}), o.map((e) => /* @__PURE__ */ g("option", {
|
|
220
|
+
value: e.id,
|
|
221
|
+
children: e.name
|
|
222
|
+
}, e.id))]
|
|
223
|
+
}),
|
|
224
|
+
t && /* @__PURE__ */ _("p", {
|
|
225
|
+
className: "text-xs text-muted-foreground",
|
|
226
|
+
children: ["Currently ", /* @__PURE__ */ g(n, { id: t })]
|
|
227
|
+
}),
|
|
228
|
+
s && o.length === 0 && /* @__PURE__ */ g("span", {
|
|
229
|
+
className: "text-xs text-muted-foreground",
|
|
230
|
+
children: "Loading members…"
|
|
231
|
+
})
|
|
232
|
+
]
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function A({ id: e, initial: t, saving: n, onSave: r, invalid: a, invalidMessage: o, multiline: s = !1, inputType: c = "text" }) {
|
|
236
|
+
let [l, u] = f(t), [d, p] = f(t), [m, h] = f(!1);
|
|
237
|
+
t !== d && l === d && (u(t), p(t));
|
|
238
|
+
let y = l !== d, b = a(l), x = async () => {
|
|
239
|
+
if (!(!y || b || m)) {
|
|
240
|
+
h(!0);
|
|
241
|
+
try {
|
|
242
|
+
await r(l) && p(l);
|
|
243
|
+
} finally {
|
|
244
|
+
h(!1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
return /* @__PURE__ */ _("div", {
|
|
249
|
+
className: "flex flex-col gap-1",
|
|
250
|
+
children: [/* @__PURE__ */ _("div", {
|
|
251
|
+
className: "flex gap-2",
|
|
252
|
+
children: [s ? /* @__PURE__ */ g("textarea", {
|
|
253
|
+
id: e,
|
|
254
|
+
className: `${E} min-h-[96px] font-mono text-xs`,
|
|
255
|
+
value: l,
|
|
256
|
+
disabled: n,
|
|
257
|
+
onChange: (e) => u(e.target.value)
|
|
258
|
+
}) : /* @__PURE__ */ g(C, {
|
|
259
|
+
id: e,
|
|
260
|
+
type: c,
|
|
261
|
+
className: "flex-1",
|
|
262
|
+
value: l,
|
|
263
|
+
disabled: n,
|
|
264
|
+
onChange: (e) => u(e.target.value)
|
|
265
|
+
}), /* @__PURE__ */ g(v, {
|
|
266
|
+
size: "sm",
|
|
267
|
+
variant: "outline",
|
|
268
|
+
disabled: !y || b || n || m,
|
|
269
|
+
onClick: () => void x(),
|
|
270
|
+
children: n || m ? "Saving…" : "Save"
|
|
271
|
+
})]
|
|
272
|
+
}), b && /* @__PURE__ */ g(i, { message: o })]
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function j({ workItemId: n, projectId: r, values: a }) {
|
|
276
|
+
let { workspaceId: s } = e(), l = t(), { data: u, loading: d, error: f } = h(c, {
|
|
277
|
+
variables: {
|
|
278
|
+
workspaceId: s ?? "",
|
|
279
|
+
projectId: r
|
|
280
|
+
},
|
|
281
|
+
skip: !s || !r
|
|
282
|
+
}), m = [...u?.planmagnetCustomFieldDefinitions.items ?? []].sort((e, t) => e.sortOrder - t.sortOrder || e.name.localeCompare(t.name)), C = new Map(a.map((e) => [e.definitionId, e]));
|
|
283
|
+
return /* @__PURE__ */ _(y, { children: [/* @__PURE__ */ _(x, {
|
|
284
|
+
className: "flex flex-row items-center justify-between space-y-0",
|
|
285
|
+
children: [/* @__PURE__ */ g(S, { children: "Custom Fields" }), /* @__PURE__ */ _(v, {
|
|
286
|
+
size: "sm",
|
|
287
|
+
variant: "outline",
|
|
288
|
+
onClick: () => l(o("/projects/project-custom-fields-page", r)),
|
|
289
|
+
children: [/* @__PURE__ */ g(p, { className: "mr-1 h-4 w-4" }), "Manage fields"]
|
|
290
|
+
})]
|
|
291
|
+
}), /* @__PURE__ */ _(b, {
|
|
292
|
+
className: "space-y-4",
|
|
293
|
+
children: [
|
|
294
|
+
d && !u && /* @__PURE__ */ g("p", {
|
|
295
|
+
className: "text-sm text-muted-foreground",
|
|
296
|
+
children: "Loading…"
|
|
297
|
+
}),
|
|
298
|
+
f && /* @__PURE__ */ g(i, { message: f.message }),
|
|
299
|
+
u && m.length === 0 && /* @__PURE__ */ g("p", {
|
|
300
|
+
className: "text-sm text-muted-foreground",
|
|
301
|
+
"data-testid": "custom-fields-panel-empty",
|
|
302
|
+
children: "This project has no custom fields yet."
|
|
303
|
+
}),
|
|
304
|
+
m.map((e) => /* @__PURE__ */ g(D, {
|
|
305
|
+
workItemId: n,
|
|
306
|
+
definition: e,
|
|
307
|
+
value: C.get(e.id)
|
|
308
|
+
}, e.id))
|
|
309
|
+
]
|
|
310
|
+
})] });
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
313
|
+
export { j as CustomFieldsPanel };
|
|
314
|
+
|
|
315
|
+
//# sourceMappingURL=CustomFieldValuesPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CustomFieldValuesPanel.js","names":[],"sources":["../../../src/planmagnet/controls/CustomFieldValuesPanel.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { Button, Card, CardContent, CardHeader, CardTitle, Input } from '@burdenoff/fe-libs/ui';\nimport { ListPlus } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ErrorLine } from './ConfirmButton';\nimport { useAssigneeOptions } from './WorkItemControls';\nimport { PersonName } from '../people/People';\nimport {\n CUSTOM_FIELD_QUERIES,\n PROJECT_CUSTOM_FIELD_DEFINITIONS,\n REMOVE_CUSTOM_FIELD_VALUE,\n SET_CUSTOM_FIELD_VALUE,\n optionStrings,\n type CustomFieldDefinitionSummary,\n type CustomFieldValueSummary,\n} from './CustomFieldControls';\n\n/**\n * A task's custom field values (BOFF-8047, capability register: Custom fields, `api-only`). The definitions come\n * from the task's own project; a value is only ever shown/edited for a definition the project still has. Every\n * write goes through `planmagnetSetCustomFieldValue` / `planmagnetRemoveCustomFieldValue` — the same fields\n * `WorkItem.customFieldValues` reads back (BOFF-7704/7839 follow-up) — and a refusal is shown, never swallowed\n * (fe-libs `errorPolicy: 'all'` resolves a failed mutation instead of rejecting it).\n */\n\nconst mutationOptions = {\n refetchQueries: CUSTOM_FIELD_QUERIES,\n awaitRefetchQueries: true,\n} as const;\n\nfunction useValueMutations(workItemId: string) {\n const [set, setState] = useMutation(SET_CUSTOM_FIELD_VALUE, mutationOptions);\n const [remove, removeState] = useMutation(REMOVE_CUSTOM_FIELD_VALUE, mutationOptions);\n const [error, setError] = useState<string | null>(null);\n\n // Returns whether the write actually landed: fe-libs `errorPolicy: 'all'` resolves a refused mutation\n // instead of rejecting it, so callers that need to know \"did this really save\" (not just \"did we ask\n // the server\") must read this return value rather than assume the awaited promise settling means success.\n const save = async (definitionId: string, value: unknown): Promise<boolean> => {\n setError(null);\n try {\n await throwOnError(set({ variables: { input: { definitionId, workItemId, value } } }));\n return true;\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The value could not be saved.');\n return false;\n }\n };\n const clear = async (definitionId: string): Promise<boolean> => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { definitionId, workItemId } }));\n return true;\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The value could not be cleared.');\n return false;\n }\n };\n return { save, clear, saving: setState.loading || removeState.loading, error };\n}\n\nconst fieldClass =\n 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\n/** A single definition's row: its current value (if any) and the control to change it. */\nfunction CustomFieldRow({\n workItemId,\n definition,\n value,\n}: {\n workItemId: string;\n definition: CustomFieldDefinitionSummary;\n value: CustomFieldValueSummary | undefined;\n}) {\n const { save, clear, saving, error } = useValueMutations(workItemId);\n const isSet = value !== undefined;\n const testId = `custom-field-value-${definition.id}`;\n\n return (\n <div className=\"space-y-1.5 border-b pb-3 last:border-0 last:pb-0\" data-testid={testId}>\n <div className=\"flex items-center justify-between gap-2\">\n <label htmlFor={testId} className=\"text-sm font-medium\">\n {definition.name}\n {definition.isRequired && <span className=\"text-destructive\"> *</span>}\n </label>\n {isSet && (\n <button\n type=\"button\"\n className=\"text-xs text-muted-foreground hover:underline disabled:opacity-60\"\n disabled={saving}\n onClick={() => void clear(definition.id)}\n >\n Clear\n </button>\n )}\n </div>\n {definition.description && (\n <p className=\"text-xs text-muted-foreground\">{definition.description}</p>\n )}\n <CustomFieldControl\n id={testId}\n definition={definition}\n value={value?.value}\n saving={saving}\n onSave={(next) => {\n // `PlanmagnetSetCustomFieldValueInput.value` is `JSON!` (non-null) in production — a control\n // clearing itself back to empty (the DATE input's native × button with no Save gate, or Save on\n // an emptied text/number/JSON field) must route through the REMOVE mutation instead, exactly\n // like the \"Clear\" link above. Nothing to remove when the row was never set — a no-op, not a\n // delete-not-found, and there was nothing to fail, so it reports success.\n if (next === null || next === undefined) {\n return isSet ? clear(definition.id) : Promise.resolve(true);\n }\n return save(definition.id, next);\n }}\n />\n {!isSet && <p className=\"text-xs text-muted-foreground\">Not set.</p>}\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** The input for one field, shaped by `fieldType`. Selects/booleans/users save on change; free text needs a\n * Save button so a keystroke never fires a request. */\nfunction CustomFieldControl({\n id,\n definition,\n value,\n saving,\n onSave,\n}: {\n id: string;\n definition: CustomFieldDefinitionSummary;\n value: unknown;\n saving: boolean;\n onSave: (value: unknown) => Promise<boolean>;\n}) {\n const type = definition.fieldType;\n\n if (type === 'BOOLEAN') {\n const checked = value === true;\n return (\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n id={id}\n type=\"checkbox\"\n checked={checked}\n disabled={saving}\n onChange={(e) => void onSave(e.target.checked)}\n />\n {checked ? 'Yes' : 'No'}\n </label>\n );\n }\n\n if (type === 'SELECT') {\n const options = optionStrings(definition.options);\n const current = typeof value === 'string' ? value : '';\n const all = current && !options.includes(current) ? [current, ...options] : options;\n return (\n <select\n id={id}\n className={fieldClass}\n value={current}\n disabled={saving}\n onChange={(e) => {\n if (e.target.value) void onSave(e.target.value);\n }}\n >\n <option value=\"\">Choose…</option>\n {all.map((o) => (\n <option key={o} value={o}>\n {o}\n </option>\n ))}\n </select>\n );\n }\n\n if (type === 'MULTI_SELECT') {\n const options = optionStrings(definition.options);\n const current = Array.isArray(value)\n ? value.filter((v): v is string => typeof v === 'string')\n : [];\n const all = [...new Set([...options, ...current])];\n const toggle = (option: string) => {\n const next = current.includes(option)\n ? current.filter((o) => o !== option)\n : [...current, option];\n void onSave(next);\n };\n return (\n <div className=\"flex flex-wrap gap-x-4 gap-y-1\">\n {all.map((o) => (\n <label key={o} className=\"flex items-center gap-1.5 text-sm\">\n <input\n type=\"checkbox\"\n checked={current.includes(o)}\n disabled={saving}\n onChange={() => toggle(o)}\n />\n {o}\n </label>\n ))}\n {all.length === 0 && (\n <span className=\"text-sm text-muted-foreground\">No options configured.</span>\n )}\n </div>\n );\n }\n\n if (type === 'USER') {\n const current = typeof value === 'string' ? value : '';\n return <UserFieldControl id={id} value={current} disabled={saving} onSave={onSave} />;\n }\n\n if (type === 'DATE') {\n // Value is stored raw (the register: value writes skip type validation) — this UI always writes an\n // ISO date-only string, the shape every other date field in this app sends.\n const current = typeof value === 'string' ? value.slice(0, 10) : '';\n return (\n <Input\n id={id}\n type=\"date\"\n disabled={saving}\n value={current}\n onChange={(e) => void onSave(e.target.value || null)}\n />\n );\n }\n\n if (type === 'NUMBER') {\n return (\n <FreeTextControl\n id={id}\n inputType=\"number\"\n initial={typeof value === 'number' ? String(value) : ''}\n saving={saving}\n onSave={(text) => {\n const trimmed = text.trim();\n if (trimmed === '') return onSave(null);\n const n = Number(trimmed);\n return Number.isFinite(n) ? onSave(n) : Promise.resolve(false);\n }}\n invalid={(text) => text.trim() !== '' && !Number.isFinite(Number(text.trim()))}\n invalidMessage=\"Enter a number.\"\n />\n );\n }\n\n if (type === 'JSON') {\n return (\n <FreeTextControl\n id={id}\n multiline\n initial={value === undefined ? '' : JSON.stringify(value, null, 2)}\n saving={saving}\n onSave={(text) => {\n const trimmed = text.trim();\n if (trimmed === '') return onSave(null);\n try {\n return onSave(JSON.parse(trimmed));\n } catch {\n return Promise.resolve(false);\n }\n }}\n invalid={(text) => {\n const trimmed = text.trim();\n if (trimmed === '') return false;\n try {\n JSON.parse(trimmed);\n return false;\n } catch {\n return true;\n }\n }}\n invalidMessage=\"That is not valid JSON.\"\n />\n );\n }\n\n // TEXT, URL, EMAIL, LABEL and anything future: a plain line of text.\n return (\n <FreeTextControl\n id={id}\n inputType={type === 'URL' ? 'url' : type === 'EMAIL' ? 'email' : 'text'}\n initial={typeof value === 'string' ? value : ''}\n saving={saving}\n onSave={(text) => onSave(text.trim() === '' ? null : text.trim())}\n invalid={() => false}\n invalidMessage=\"\"\n />\n );\n}\n\n/** A user-id value: the workspace directory picker used elsewhere (BOFF-7946), '' clears the value. */\nfunction UserFieldControl({\n id,\n value,\n disabled,\n onSave,\n}: {\n id: string;\n value: string;\n disabled: boolean;\n onSave: (value: unknown) => Promise<boolean>;\n}) {\n const { options, loading } = useAssigneeOptions(value || null);\n return (\n <div className=\"flex flex-col gap-1\">\n <select\n id={id}\n className={fieldClass}\n value={value}\n disabled={disabled}\n onChange={(e) => {\n if (e.target.value) void onSave(e.target.value);\n }}\n >\n <option value=\"\">Choose a member…</option>\n {options.map((o) => (\n <option key={o.id} value={o.id}>\n {o.name}\n </option>\n ))}\n </select>\n {value && (\n <p className=\"text-xs text-muted-foreground\">\n Currently <PersonName id={value} />\n </p>\n )}\n {loading && options.length === 0 && (\n <span className=\"text-xs text-muted-foreground\">Loading members…</span>\n )}\n </div>\n );\n}\n\n/** Text/number/JSON entry: local draft state so typing never fires a request, an explicit Save button so\n * one does fire exactly once. */\nfunction FreeTextControl({\n id,\n initial,\n saving,\n onSave,\n invalid,\n invalidMessage,\n multiline = false,\n inputType = 'text',\n}: {\n id: string;\n initial: string;\n saving: boolean;\n onSave: (text: string) => Promise<boolean>;\n invalid: (text: string) => boolean;\n invalidMessage: string;\n multiline?: boolean;\n inputType?: string;\n}) {\n const [draft, setDraft] = useState(initial);\n const [lastSaved, setLastSaved] = useState(initial);\n // Tracks the in-flight commit separately from the parent's `saving` (the mutation's own loading state):\n // it covers the gap between clicking Save and that loading flag actually flipping true, so a second click\n // can never fire a duplicate request.\n const [committing, setCommitting] = useState(false);\n // The definition's own value changed under us (a save completed, or another editor's did): pick it up,\n // but only while this control has no unsaved edit of its own.\n if (initial !== lastSaved && draft === lastSaved) {\n setDraft(initial);\n setLastSaved(initial);\n }\n const dirty = draft !== lastSaved;\n const bad = invalid(draft);\n\n const commit = async () => {\n if (!dirty || bad || committing) return;\n setCommitting(true);\n try {\n // Only a CONFIRMED success may mark the draft as saved. fe-libs resolves a refused mutation instead\n // of rejecting it (errorPolicy: 'all'), so `await onSave(draft)` alone settling is not success — the\n // Save button must stay enabled (and the draft stay in the box) until `onSave` reports `true`, or a\n // refusal would look saved while the write never landed.\n const ok = await onSave(draft);\n if (ok) setLastSaved(draft);\n } finally {\n setCommitting(false);\n }\n };\n\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex gap-2\">\n {multiline ? (\n <textarea\n id={id}\n className={`${fieldClass} min-h-[96px] font-mono text-xs`}\n value={draft}\n disabled={saving}\n onChange={(e) => setDraft(e.target.value)}\n />\n ) : (\n <Input\n id={id}\n type={inputType}\n className=\"flex-1\"\n value={draft}\n disabled={saving}\n onChange={(e) => setDraft(e.target.value)}\n />\n )}\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={!dirty || bad || saving || committing}\n onClick={() => void commit()}\n >\n {saving || committing ? 'Saving…' : 'Save'}\n </Button>\n </div>\n {bad && <ErrorLine message={invalidMessage} />}\n </div>\n );\n}\n\nexport function CustomFieldsPanel({\n workItemId,\n projectId,\n values,\n}: {\n workItemId: string;\n projectId: string;\n values: CustomFieldValueSummary[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n const { data, loading, error } = useQuery<{\n planmagnetCustomFieldDefinitions: { items: CustomFieldDefinitionSummary[] };\n }>(PROJECT_CUSTOM_FIELD_DEFINITIONS, {\n variables: { workspaceId: workspaceId ?? '', projectId },\n skip: !workspaceId || !projectId,\n });\n const definitions = [...(data?.planmagnetCustomFieldDefinitions.items ?? [])].sort(\n (a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name)\n );\n const byDefinition = new Map(values.map((v) => [v.definitionId, v]));\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>Custom Fields</CardTitle>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => navigate(detailPath('/projects/project-custom-fields-page', projectId))}\n >\n <ListPlus className=\"mr-1 h-4 w-4\" />\n Manage fields\n </Button>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {/* Only before the first load: a refetch (any value save) must not blank every other field's state. */}\n {loading && !data && <p className=\"text-sm text-muted-foreground\">Loading…</p>}\n {error && <ErrorLine message={error.message} />}\n {data && definitions.length === 0 && (\n <p className=\"text-sm text-muted-foreground\" data-testid=\"custom-fields-panel-empty\">\n This project has no custom fields yet.\n </p>\n )}\n {definitions.map((def) => (\n <CustomFieldRow\n key={def.id}\n workItemId={workItemId}\n definition={def}\n value={byDefinition.get(def.id)}\n />\n ))}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { LEAVES_PAGE_CONTEXT as t, throwOnError as n } from "../utils/mutationResult.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { ConfirmButton as r, ErrorLine as i } from "./ConfirmButton.js";
|
|
4
|
+
import { ProjectSelect as a, WorkItemSearchSelect as o } from "./Pickers.js";
|
|
5
|
+
import { enumLabel as s } from "./WorkItemControls.js";
|
|
6
|
+
import { detailPath as c } from "../utils/rowNavigation.js";
|
|
7
|
+
import { parseIntField as l } from "../utils/formValidation.js";
|
|
8
8
|
import { useState as u } from "react";
|
|
9
9
|
import { BarChart3 as d, Pencil as f, ThumbsUp as p, Trash2 as m } from "lucide-react";
|
|
10
10
|
import { gql as h } from "@apollo/client";
|
|
@@ -60,7 +60,7 @@ var O = [
|
|
|
60
60
|
awaitRefetchQueries: !0
|
|
61
61
|
};
|
|
62
62
|
function F({ featureId: e, voted: t }) {
|
|
63
|
-
let [r,
|
|
63
|
+
let [r, a] = g(A, P), [o, s] = g(j, P), [c, l] = u(null), d = a.loading || s.loading, f = async () => {
|
|
64
64
|
l(null);
|
|
65
65
|
try {
|
|
66
66
|
await n((t ? o : r)({ variables: { id: e } }));
|
|
@@ -78,18 +78,18 @@ function F({ featureId: e, voted: t }) {
|
|
|
78
78
|
disabled: d,
|
|
79
79
|
onClick: () => void f(),
|
|
80
80
|
children: [/* @__PURE__ */ _(p, { className: "mr-1 h-4 w-4" }), t ? "Voted" : "Vote"]
|
|
81
|
-
}), c && /* @__PURE__ */ _(
|
|
81
|
+
}), c && /* @__PURE__ */ _(i, { message: c })]
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
|
-
function I({ feature:
|
|
85
|
-
let
|
|
84
|
+
function I({ feature: a }) {
|
|
85
|
+
let o = e(), [l, f] = g(M, P), [p, h] = g(N, {
|
|
86
86
|
refetchQueries: k.filter((e) => e !== "GetFeatureRequest"),
|
|
87
87
|
context: t
|
|
88
|
-
}), [b, x] = u(null), S = f.loading || h.loading, C = O.includes(
|
|
88
|
+
}), [b, x] = u(null), S = f.loading || h.loading, C = O.includes(a.status) ? O : [a.status, ...O], w = async (e) => {
|
|
89
89
|
x(null);
|
|
90
90
|
try {
|
|
91
|
-
await n(
|
|
92
|
-
id:
|
|
91
|
+
await n(l({ variables: {
|
|
92
|
+
id: a.id,
|
|
93
93
|
input: { status: e }
|
|
94
94
|
} }));
|
|
95
95
|
} catch (e) {
|
|
@@ -98,7 +98,7 @@ function I({ feature: o }) {
|
|
|
98
98
|
}, T = async () => {
|
|
99
99
|
x(null);
|
|
100
100
|
try {
|
|
101
|
-
await n(p({ variables: { id:
|
|
101
|
+
await n(p({ variables: { id: a.id } })), o("/products/features-page");
|
|
102
102
|
} catch (e) {
|
|
103
103
|
x(e instanceof Error ? e.message : "The request could not be deleted.");
|
|
104
104
|
}
|
|
@@ -112,29 +112,29 @@ function I({ feature: o }) {
|
|
|
112
112
|
"aria-label": "Status",
|
|
113
113
|
"data-testid": "feature-status",
|
|
114
114
|
className: "rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60",
|
|
115
|
-
value:
|
|
115
|
+
value: a.status,
|
|
116
116
|
disabled: S,
|
|
117
117
|
onChange: (e) => {
|
|
118
|
-
e.target.value !==
|
|
118
|
+
e.target.value !== a.status && w(e.target.value);
|
|
119
119
|
},
|
|
120
120
|
children: C.map((e) => /* @__PURE__ */ _("option", {
|
|
121
121
|
value: e,
|
|
122
|
-
children:
|
|
122
|
+
children: s(e)
|
|
123
123
|
}, e))
|
|
124
124
|
}),
|
|
125
|
-
/* @__PURE__ */ _(L, { feature:
|
|
125
|
+
/* @__PURE__ */ _(L, { feature: a }),
|
|
126
126
|
/* @__PURE__ */ v(y, {
|
|
127
127
|
size: "sm",
|
|
128
128
|
variant: "outline",
|
|
129
129
|
"data-testid": "feature-analytics",
|
|
130
|
-
onClick: () =>
|
|
130
|
+
onClick: () => o(c("/products/feature-analytics-page", a.id)),
|
|
131
131
|
children: [/* @__PURE__ */ _(d, { className: "mr-1 h-4 w-4" }), "Analytics"]
|
|
132
132
|
}),
|
|
133
|
-
/* @__PURE__ */ _(
|
|
133
|
+
/* @__PURE__ */ _(r, {
|
|
134
134
|
label: "Delete",
|
|
135
135
|
variant: "outline",
|
|
136
136
|
icon: /* @__PURE__ */ _(m, { className: "mr-1 h-4 w-4" }),
|
|
137
|
-
title: `Delete "${
|
|
137
|
+
title: `Delete "${a.title}"?`,
|
|
138
138
|
description: "The request and its votes are deleted. This cannot be undone here.",
|
|
139
139
|
confirmLabel: "Delete request",
|
|
140
140
|
destructive: !0,
|
|
@@ -142,7 +142,7 @@ function I({ feature: o }) {
|
|
|
142
142
|
onConfirm: () => void T()
|
|
143
143
|
})
|
|
144
144
|
]
|
|
145
|
-
}), b && /* @__PURE__ */ _(
|
|
145
|
+
}), b && /* @__PURE__ */ _(i, { message: b })]
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
148
|
function L({ feature: e }) {
|
|
@@ -153,35 +153,35 @@ function L({ feature: e }) {
|
|
|
153
153
|
effortEstimate: e.effortEstimate ?? "",
|
|
154
154
|
projectId: e.projectId ?? "",
|
|
155
155
|
workItemId: e.workItemId ?? ""
|
|
156
|
-
}, [r,
|
|
156
|
+
}, [r, s] = u(!1), [c, d] = u(t), [p, m] = u(null), [h, O] = g(M, P), k = l(c.businessValue, {
|
|
157
157
|
min: 0,
|
|
158
158
|
max: 100
|
|
159
|
-
}), A =
|
|
159
|
+
}), A = c.title.trim() !== "" && !k.invalid && !O.loading, j = async () => {
|
|
160
160
|
m(null);
|
|
161
|
-
let r = (e) => e.trim() ? e.trim() : null,
|
|
162
|
-
title:
|
|
163
|
-
description: r(
|
|
161
|
+
let r = (e) => e.trim() ? e.trim() : null, i = {
|
|
162
|
+
title: c.title.trim(),
|
|
163
|
+
description: r(c.description),
|
|
164
164
|
businessValue: k.value ?? null,
|
|
165
|
-
effortEstimate: r(
|
|
166
|
-
projectId:
|
|
167
|
-
workItemId:
|
|
168
|
-
},
|
|
165
|
+
effortEstimate: r(c.effortEstimate),
|
|
166
|
+
projectId: c.projectId || null,
|
|
167
|
+
workItemId: c.workItemId || null
|
|
168
|
+
}, a = {
|
|
169
169
|
title: t.title,
|
|
170
170
|
description: r(t.description),
|
|
171
171
|
businessValue: t.businessValue === "" ? null : Number(t.businessValue),
|
|
172
172
|
effortEstimate: r(t.effortEstimate),
|
|
173
173
|
projectId: t.projectId || null,
|
|
174
174
|
workItemId: t.workItemId || null
|
|
175
|
-
},
|
|
176
|
-
if (Object.keys(
|
|
177
|
-
|
|
175
|
+
}, o = Object.fromEntries(Object.entries(i).filter(([e, t]) => t !== a[e]));
|
|
176
|
+
if (Object.keys(o).length === 0) {
|
|
177
|
+
s(!1);
|
|
178
178
|
return;
|
|
179
179
|
}
|
|
180
180
|
try {
|
|
181
181
|
await n(h({ variables: {
|
|
182
182
|
id: e.id,
|
|
183
|
-
input:
|
|
184
|
-
} })),
|
|
183
|
+
input: o
|
|
184
|
+
} })), s(!1);
|
|
185
185
|
} catch (e) {
|
|
186
186
|
m(e instanceof Error ? e.message : "The request could not be saved.");
|
|
187
187
|
}
|
|
@@ -189,7 +189,7 @@ function L({ feature: e }) {
|
|
|
189
189
|
return /* @__PURE__ */ v(x, {
|
|
190
190
|
open: r,
|
|
191
191
|
onOpenChange: (e) => {
|
|
192
|
-
|
|
192
|
+
s(e), e ? d(t) : m(null);
|
|
193
193
|
},
|
|
194
194
|
children: [/* @__PURE__ */ _(D, {
|
|
195
195
|
asChild: !0,
|
|
@@ -213,9 +213,9 @@ function L({ feature: e }) {
|
|
|
213
213
|
}), /* @__PURE__ */ _(b, {
|
|
214
214
|
id: "feature-edit-title",
|
|
215
215
|
className: "mt-1",
|
|
216
|
-
value:
|
|
216
|
+
value: c.title,
|
|
217
217
|
onChange: (e) => d({
|
|
218
|
-
...
|
|
218
|
+
...c,
|
|
219
219
|
title: e.target.value
|
|
220
220
|
})
|
|
221
221
|
})] }),
|
|
@@ -226,9 +226,9 @@ function L({ feature: e }) {
|
|
|
226
226
|
}), /* @__PURE__ */ _("textarea", {
|
|
227
227
|
id: "feature-edit-description",
|
|
228
228
|
className: "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]",
|
|
229
|
-
value:
|
|
229
|
+
value: c.description,
|
|
230
230
|
onChange: (e) => d({
|
|
231
|
-
...
|
|
231
|
+
...c,
|
|
232
232
|
description: e.target.value
|
|
233
233
|
})
|
|
234
234
|
})] }),
|
|
@@ -244,10 +244,10 @@ function L({ feature: e }) {
|
|
|
244
244
|
min: 0,
|
|
245
245
|
max: 100,
|
|
246
246
|
className: "mt-1",
|
|
247
|
-
value:
|
|
247
|
+
value: c.businessValue,
|
|
248
248
|
"aria-invalid": k.invalid,
|
|
249
249
|
onChange: (e) => d({
|
|
250
|
-
...
|
|
250
|
+
...c,
|
|
251
251
|
businessValue: e.target.value
|
|
252
252
|
})
|
|
253
253
|
})] }), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
@@ -258,26 +258,26 @@ function L({ feature: e }) {
|
|
|
258
258
|
id: "feature-edit-effort",
|
|
259
259
|
className: "mt-1",
|
|
260
260
|
placeholder: "e.g. M",
|
|
261
|
-
value:
|
|
261
|
+
value: c.effortEstimate,
|
|
262
262
|
onChange: (e) => d({
|
|
263
|
-
...
|
|
263
|
+
...c,
|
|
264
264
|
effortEstimate: e.target.value
|
|
265
265
|
})
|
|
266
266
|
})] })]
|
|
267
267
|
}),
|
|
268
|
-
k.invalid && /* @__PURE__ */ _(
|
|
268
|
+
k.invalid && /* @__PURE__ */ _(i, { message: "Business value is a whole number from 0 to 100." }),
|
|
269
269
|
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("label", {
|
|
270
270
|
htmlFor: "feature-edit-project",
|
|
271
271
|
className: N,
|
|
272
272
|
children: "Project"
|
|
273
273
|
}), /* @__PURE__ */ _("div", {
|
|
274
274
|
className: "mt-1",
|
|
275
|
-
children: /* @__PURE__ */ _(
|
|
275
|
+
children: /* @__PURE__ */ _(a, {
|
|
276
276
|
id: "feature-edit-project",
|
|
277
|
-
value:
|
|
277
|
+
value: c.projectId,
|
|
278
278
|
noneLabel: "No project",
|
|
279
279
|
onChange: (e) => d({
|
|
280
|
-
...
|
|
280
|
+
...c,
|
|
281
281
|
projectId: e,
|
|
282
282
|
workItemId: ""
|
|
283
283
|
})
|
|
@@ -289,22 +289,22 @@ function L({ feature: e }) {
|
|
|
289
289
|
children: "Delivered by work item"
|
|
290
290
|
}), /* @__PURE__ */ _("div", {
|
|
291
291
|
className: "mt-1",
|
|
292
|
-
children: /* @__PURE__ */ _(
|
|
292
|
+
children: /* @__PURE__ */ _(o, {
|
|
293
293
|
id: "feature-edit-workitem",
|
|
294
|
-
projectId:
|
|
295
|
-
value:
|
|
294
|
+
projectId: c.projectId || void 0,
|
|
295
|
+
value: c.workItemId,
|
|
296
296
|
onChange: (e) => d({
|
|
297
|
-
...
|
|
297
|
+
...c,
|
|
298
298
|
workItemId: e
|
|
299
299
|
})
|
|
300
300
|
})
|
|
301
301
|
})] }),
|
|
302
|
-
p && /* @__PURE__ */ _(
|
|
302
|
+
p && /* @__PURE__ */ _(i, { message: p })
|
|
303
303
|
]
|
|
304
304
|
}),
|
|
305
305
|
/* @__PURE__ */ v(w, { children: [/* @__PURE__ */ _(y, {
|
|
306
306
|
variant: "outline",
|
|
307
|
-
onClick: () =>
|
|
307
|
+
onClick: () => s(!1),
|
|
308
308
|
children: "Cancel"
|
|
309
309
|
}), /* @__PURE__ */ _(y, {
|
|
310
310
|
onClick: () => void j(),
|