@burdenoff/microfe-planmagnet 2026.928.6 → 2026.928.8
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/generated/wspace-operations.d.ts +5 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +15 -0
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -1
- package/dist/planmagnet/automation/AutomationRuleFields.js +13 -12
- package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -1
- 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 +45 -8
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +74 -59
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +153 -63
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +138 -112
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +77 -70
- package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutomationRuleFields.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/automation/AutomationRuleFields.tsx"],"names":[],"mappings":"AASA,OAAO,EAWL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,uBAAuB,CAAC;AAE/B,+DAA+D;AAC/D,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"AutomationRuleFields.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/automation/AutomationRuleFields.tsx"],"names":[],"mappings":"AASA,OAAO,EAWL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,uBAAuB,CAAC;AAE/B,+DAA+D;AAC/D,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAoED,8GAA8G;AAC9G,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;CACxC,2CA0EA;AAED,8GAA8G;AAC9G,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,QAAQ,GACT,EAAE;IACD,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,IAAI,CAAC;CAC3C,2CA4EA;AAED,sGAAsG;AACtG,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,QAAQ,EACR,aAAqB,GACtB,EAAE;IACD,IAAI,EAAE,uBAAuB,CAAC;IAC9B,QAAQ,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAClD,0FAA0F;IAC1F,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,2CA6FA"}
|
|
@@ -5,8 +5,8 @@ import { Plus as p, X as m } from "lucide-react";
|
|
|
5
5
|
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
6
6
|
import { Button as _, Input as v } from "@burdenoff/fe-libs/ui";
|
|
7
7
|
//#region src/planmagnet/automation/AutomationRuleFields.tsx
|
|
8
|
-
var y = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60";
|
|
9
|
-
function
|
|
8
|
+
var y = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60", b = "text-foreground";
|
|
9
|
+
function x({ action: e, onChange: a }) {
|
|
10
10
|
switch (e.type) {
|
|
11
11
|
case "ASSIGN_TO_USER": return /* @__PURE__ */ h(t, {
|
|
12
12
|
value: e.value,
|
|
@@ -38,13 +38,14 @@ function b({ action: e, onChange: a }) {
|
|
|
38
38
|
});
|
|
39
39
|
default: return /* @__PURE__ */ h(v, {
|
|
40
40
|
"aria-label": "Label",
|
|
41
|
+
className: b,
|
|
41
42
|
placeholder: "e.g. needs-review",
|
|
42
43
|
value: e.value,
|
|
43
44
|
onChange: (e) => a(e.target.value)
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
|
-
function
|
|
48
|
+
function S({ actions: e, onChange: t }) {
|
|
48
49
|
let n = (n, r) => t(e.map((e, t) => t === n ? {
|
|
49
50
|
...e,
|
|
50
51
|
...r
|
|
@@ -74,7 +75,7 @@ function x({ actions: e, onChange: t }) {
|
|
|
74
75
|
}),
|
|
75
76
|
/* @__PURE__ */ h("div", {
|
|
76
77
|
className: "min-w-0 flex-1",
|
|
77
|
-
children: /* @__PURE__ */ h(
|
|
78
|
+
children: /* @__PURE__ */ h(x, {
|
|
78
79
|
action: e,
|
|
79
80
|
onChange: (e) => n(t, { value: e })
|
|
80
81
|
})
|
|
@@ -112,7 +113,7 @@ function x({ actions: e, onChange: t }) {
|
|
|
112
113
|
]
|
|
113
114
|
});
|
|
114
115
|
}
|
|
115
|
-
function
|
|
116
|
+
function C({ conditions: e, onChange: t }) {
|
|
116
117
|
let n = (n, r) => t(e.map((e, t) => t === n ? {
|
|
117
118
|
...e,
|
|
118
119
|
...r
|
|
@@ -126,7 +127,7 @@ function S({ conditions: e, onChange: t }) {
|
|
|
126
127
|
/* @__PURE__ */ h(v, {
|
|
127
128
|
"aria-label": "Condition field",
|
|
128
129
|
list: "automation-condition-fields",
|
|
129
|
-
className:
|
|
130
|
+
className: `w-36 shrink-0 ${b}`,
|
|
130
131
|
placeholder: "field",
|
|
131
132
|
value: e.field,
|
|
132
133
|
onChange: (e) => n(t, { field: e.target.value })
|
|
@@ -143,7 +144,7 @@ function S({ conditions: e, onChange: t }) {
|
|
|
143
144
|
}),
|
|
144
145
|
e.operator !== "exists" && /* @__PURE__ */ h(v, {
|
|
145
146
|
"aria-label": "Condition value",
|
|
146
|
-
className:
|
|
147
|
+
className: `min-w-0 flex-1 ${b}`,
|
|
147
148
|
placeholder: e.operator === "in" || e.operator === "notIn" ? "comma, separated, values" : "value",
|
|
148
149
|
value: e.value,
|
|
149
150
|
onChange: (e) => n(t, { value: e.target.value })
|
|
@@ -181,7 +182,7 @@ function S({ conditions: e, onChange: t }) {
|
|
|
181
182
|
]
|
|
182
183
|
});
|
|
183
184
|
}
|
|
184
|
-
function
|
|
185
|
+
function w({ form: t, onChange: n, projectLocked: r = !1 }) {
|
|
185
186
|
return /* @__PURE__ */ g("div", {
|
|
186
187
|
className: "space-y-4",
|
|
187
188
|
children: [
|
|
@@ -191,7 +192,7 @@ function C({ form: t, onChange: n, projectLocked: r = !1 }) {
|
|
|
191
192
|
children: "Name"
|
|
192
193
|
}), /* @__PURE__ */ h(v, {
|
|
193
194
|
id: "automation-name",
|
|
194
|
-
className:
|
|
195
|
+
className: `mt-1 ${b}`,
|
|
195
196
|
value: t.name,
|
|
196
197
|
onChange: (e) => n({
|
|
197
198
|
...t,
|
|
@@ -274,7 +275,7 @@ function C({ form: t, onChange: n, projectLocked: r = !1 }) {
|
|
|
274
275
|
className: "mb-2 text-xs text-muted-foreground",
|
|
275
276
|
children: "All conditions must match. Leave empty to match every event."
|
|
276
277
|
}),
|
|
277
|
-
/* @__PURE__ */ h(
|
|
278
|
+
/* @__PURE__ */ h(C, {
|
|
278
279
|
conditions: t.conditions,
|
|
279
280
|
onChange: (e) => n({
|
|
280
281
|
...t,
|
|
@@ -291,7 +292,7 @@ function C({ form: t, onChange: n, projectLocked: r = !1 }) {
|
|
|
291
292
|
className: "mb-2 text-xs text-muted-foreground",
|
|
292
293
|
children: "Run in order when the conditions match."
|
|
293
294
|
}),
|
|
294
|
-
/* @__PURE__ */ h(
|
|
295
|
+
/* @__PURE__ */ h(S, {
|
|
295
296
|
actions: t.actions,
|
|
296
297
|
onChange: (e) => n({
|
|
297
298
|
...t,
|
|
@@ -303,6 +304,6 @@ function C({ form: t, onChange: n, projectLocked: r = !1 }) {
|
|
|
303
304
|
});
|
|
304
305
|
}
|
|
305
306
|
//#endregion
|
|
306
|
-
export {
|
|
307
|
+
export { w as AutomationRuleFields };
|
|
307
308
|
|
|
308
309
|
//# sourceMappingURL=AutomationRuleFields.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutomationRuleFields.js","names":[],"sources":["../../../src/planmagnet/automation/AutomationRuleFields.tsx"],"sourcesContent":["import { Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Plus, X } from 'lucide-react';\nimport {\n AssigneeSelect,\n WORK_ITEM_PRIORITIES,\n WORK_ITEM_STATUSES,\n enumLabel,\n} from '../controls/WorkItemControls';\nimport { ProjectSelect } from '../controls/Pickers';\nimport {\n ACTION_TYPES,\n ACTION_TYPE_LABEL,\n AUTOMATIC_TRIGGER_TYPES,\n CONDITION_FIELDS,\n CONDITION_OPERATORS,\n CONDITION_OPERATOR_LABEL,\n TRIGGER_TYPES,\n TRIGGER_TYPE_LABEL,\n type ActionType,\n type ConditionOperator,\n type RuleAction,\n type RuleCondition,\n type TriggerType,\n} from './automationRuleTypes';\n\n/** The editable shape both the create and edit pages share. */\nexport interface AutomationRuleFormState {\n name: string;\n description: string;\n projectId: string;\n triggerType: TriggerType;\n conditions: RuleCondition[];\n actions: RuleAction[];\n}\n\nconst fieldClass =\n 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\nfunction ActionValueEditor({\n action,\n onChange,\n}: {\n action: RuleAction;\n onChange: (value: string) => void;\n}) {\n switch (action.type) {\n case 'ASSIGN_TO_USER':\n return <AssigneeSelect value={action.value} onChange={onChange} />;\n case 'SET_STATUS':\n return (\n <select\n className={fieldClass}\n value={action.value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">Choose a status</option>\n {WORK_ITEM_STATUSES.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n );\n case 'SET_PRIORITY':\n return (\n <select\n className={fieldClass}\n value={action.value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">Choose a priority</option>\n {WORK_ITEM_PRIORITIES.map((p) => (\n <option key={p} value={p}>\n {enumLabel(p)}\n </option>\n ))}\n </select>\n );\n case 'ADD_LABEL':\n default:\n return (\n <Input\n aria-label=\"Label\"\n placeholder=\"e.g. needs-review\"\n value={action.value}\n onChange={(e) => onChange(e.target.value)}\n />\n );\n }\n}\n\n/** The actions builder: only the four types the engine can actually dispatch (see automationRuleTypes.ts). */\nexport function ActionsEditor({\n actions,\n onChange,\n}: {\n actions: RuleAction[];\n onChange: (next: RuleAction[]) => void;\n}) {\n const update = (index: number, patch: Partial<RuleAction>) =>\n onChange(actions.map((a, i) => (i === index ? { ...a, ...patch } : a)));\n const remove = (index: number) => onChange(actions.filter((_, i) => i !== index));\n const add = () => onChange([...actions, { type: 'ASSIGN_TO_USER', value: '' }]);\n\n return (\n <div className=\"space-y-2\">\n {actions.map((action, index) => {\n // A rule created before this UI existed (or via the API directly) can carry an action type this\n // builder does not offer (e.g. POST_COMMENT, which the engine never dispatches — see\n // automationRuleTypes.ts). Keep it selectable and visible rather than silently swapping it out from\n // under the user the moment they open the edit page.\n const known = (ACTION_TYPES as readonly string[]).includes(action.type);\n const typeOptions = known ? ACTION_TYPES : [action.type, ...ACTION_TYPES];\n return (\n <div key={index} className=\"space-y-1\">\n <div className=\"flex items-start gap-2\">\n <select\n aria-label=\"Action type\"\n className={`${fieldClass} mt-0 w-40 shrink-0`}\n value={action.type}\n onChange={(e) =>\n update(index, {\n type: e.target.value as ActionType,\n value: e.target.value === action.type ? action.value : '',\n })\n }\n >\n {typeOptions.map((t) => (\n <option key={t} value={t}>\n {known || t !== action.type ? (ACTION_TYPE_LABEL[t as ActionType] ?? t) : t}\n </option>\n ))}\n </select>\n <div className=\"min-w-0 flex-1\">\n <ActionValueEditor action={action} onChange={(value) => update(index, { value })} />\n </div>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n aria-label=\"Remove action\"\n onClick={() => remove(index)}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n {!known && (\n <p className=\"text-xs text-muted-foreground\">\n This action type is not run by the engine yet; the rule keeps it but never\n dispatches it.\n </p>\n )}\n </div>\n );\n })}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={add}\n data-testid=\"automation-add-action\"\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n Add action\n </Button>\n {actions.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">\n A rule with no actions still records that it ran, but changes nothing.\n </p>\n )}\n </div>\n );\n}\n\n/** The conditions builder. A rule with no conditions always matches — the gate is opt-in (engine default). */\nexport function ConditionsEditor({\n conditions,\n onChange,\n}: {\n conditions: RuleCondition[];\n onChange: (next: RuleCondition[]) => void;\n}) {\n const update = (index: number, patch: Partial<RuleCondition>) =>\n onChange(conditions.map((c, i) => (i === index ? { ...c, ...patch } : c)));\n const remove = (index: number) => onChange(conditions.filter((_, i) => i !== index));\n const add = () => onChange([...conditions, { field: 'status', operator: 'eq', value: '' }]);\n\n return (\n <div className=\"space-y-2\">\n {conditions.map((condition, index) => (\n <div key={index} className=\"flex items-start gap-2\">\n <Input\n aria-label=\"Condition field\"\n list=\"automation-condition-fields\"\n className=\"w-36 shrink-0\"\n placeholder=\"field\"\n value={condition.field}\n onChange={(e) => update(index, { field: e.target.value })}\n />\n <select\n aria-label=\"Condition operator\"\n className={`${fieldClass} mt-0 w-32 shrink-0`}\n value={condition.operator}\n onChange={(e) => update(index, { operator: e.target.value as ConditionOperator })}\n >\n {CONDITION_OPERATORS.map((op) => (\n <option key={op} value={op}>\n {CONDITION_OPERATOR_LABEL[op]}\n </option>\n ))}\n </select>\n {condition.operator !== 'exists' && (\n <Input\n aria-label=\"Condition value\"\n className=\"min-w-0 flex-1\"\n placeholder={\n condition.operator === 'in' || condition.operator === 'notIn'\n ? 'comma, separated, values'\n : 'value'\n }\n value={condition.value}\n onChange={(e) => update(index, { value: e.target.value })}\n />\n )}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n aria-label=\"Remove condition\"\n onClick={() => remove(index)}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n ))}\n <datalist id=\"automation-condition-fields\">\n {CONDITION_FIELDS.map((f) => (\n <option key={f} value={f} />\n ))}\n </datalist>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={add}\n data-testid=\"automation-add-condition\"\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n Add condition\n </Button>\n {conditions.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">\n No conditions: the rule matches every event.\n </p>\n )}\n </div>\n );\n}\n\n/** Name, description, project, trigger, conditions and actions — the fields create and edit share. */\nexport function AutomationRuleFields({\n form,\n onChange,\n projectLocked = false,\n}: {\n form: AutomationRuleFormState;\n onChange: (next: AutomationRuleFormState) => void;\n /** The edit page cannot change the project (the API accepts no `projectId` on update). */\n projectLocked?: boolean;\n}) {\n return (\n <div className=\"space-y-4\">\n <div>\n <label htmlFor=\"automation-name\" className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id=\"automation-name\"\n className=\"mt-1\"\n value={form.name}\n onChange={(e) => onChange({ ...form, name: e.target.value })}\n placeholder=\"e.g. Assign new bugs to triage\"\n />\n </div>\n <div>\n <label htmlFor=\"automation-description\" className=\"text-sm font-medium\">\n Description\n </label>\n <textarea\n id=\"automation-description\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]\"\n value={form.description}\n onChange={(e) => onChange({ ...form, description: e.target.value })}\n placeholder=\"What this rule is for\"\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"automation-project\" className=\"text-sm font-medium\">\n Project\n </label>\n {projectLocked ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n A rule's project cannot be changed after it is created.\n </p>\n ) : (\n <div className=\"mt-1\">\n <ProjectSelect\n id=\"automation-project\"\n value={form.projectId}\n onChange={(projectId) => onChange({ ...form, projectId })}\n />\n </div>\n )}\n </div>\n <div>\n <label htmlFor=\"automation-trigger\" className=\"text-sm font-medium\">\n Trigger\n </label>\n <select\n id=\"automation-trigger\"\n className={fieldClass}\n value={form.triggerType}\n onChange={(e) => onChange({ ...form, triggerType: e.target.value as TriggerType })}\n >\n {TRIGGER_TYPES.map((t) => (\n <option key={t} value={t}>\n {TRIGGER_TYPE_LABEL[t]}\n {AUTOMATIC_TRIGGER_TYPES.has(t) ? '' : ' (not yet automatic)'}\n </option>\n ))}\n </select>\n {!AUTOMATIC_TRIGGER_TYPES.has(form.triggerType) && (\n <p className=\"mt-1 text-xs text-muted-foreground\">\n This trigger is saved but does not fire on its own yet — use{' '}\n <span className=\"font-medium\">Run now</span> to run it against a work item.\n </p>\n )}\n </div>\n </div>\n <div>\n <p className=\"text-sm font-medium\">Conditions</p>\n <p className=\"mb-2 text-xs text-muted-foreground\">\n All conditions must match. Leave empty to match every event.\n </p>\n <ConditionsEditor\n conditions={form.conditions}\n onChange={(conditions) => onChange({ ...form, conditions })}\n />\n </div>\n <div>\n <p className=\"text-sm font-medium\">Actions</p>\n <p className=\"mb-2 text-xs text-muted-foreground\">\n Run in order when the conditions match.\n </p>\n <ActionsEditor\n actions={form.actions}\n onChange={(actions) => onChange({ ...form, actions })}\n />\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AutomationRuleFields.js","names":[],"sources":["../../../src/planmagnet/automation/AutomationRuleFields.tsx"],"sourcesContent":["import { Button, Input } from '@burdenoff/fe-libs/ui';\nimport { Plus, X } from 'lucide-react';\nimport {\n AssigneeSelect,\n WORK_ITEM_PRIORITIES,\n WORK_ITEM_STATUSES,\n enumLabel,\n} from '../controls/WorkItemControls';\nimport { ProjectSelect } from '../controls/Pickers';\nimport {\n ACTION_TYPES,\n ACTION_TYPE_LABEL,\n AUTOMATIC_TRIGGER_TYPES,\n CONDITION_FIELDS,\n CONDITION_OPERATORS,\n CONDITION_OPERATOR_LABEL,\n TRIGGER_TYPES,\n TRIGGER_TYPE_LABEL,\n type ActionType,\n type ConditionOperator,\n type RuleAction,\n type RuleCondition,\n type TriggerType,\n} from './automationRuleTypes';\n\n/** The editable shape both the create and edit pages share. */\nexport interface AutomationRuleFormState {\n name: string;\n description: string;\n projectId: string;\n triggerType: TriggerType;\n conditions: RuleCondition[];\n actions: RuleAction[];\n}\n\nconst fieldClass =\n 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\n// fe-libs' <Input> sets its own text color via `text-text-primary` (a semantic token from its own theme\n// scope), but every Input below sits in a row that also carries this file's OTHER token family\n// (`fieldClass` above uses `border-input`/`bg-background`, and the rest of this microfe standardizes on\n// `text-foreground`/`bg-background`/`border-input`). Passing this class merges via `cn()` (tailwind-merge),\n// which recognizes both `text-text-primary` and `text-foreground` as the same \"text color\" group and keeps\n// only the last one — so this pins every value/label an editor renders here to the token this microfe's own\n// pages already render correctly with, instead of leaving it to whichever token happens to win the cascade.\nconst inputTextClass = 'text-foreground';\n\nfunction ActionValueEditor({\n action,\n onChange,\n}: {\n action: RuleAction;\n onChange: (value: string) => void;\n}) {\n switch (action.type) {\n case 'ASSIGN_TO_USER':\n return <AssigneeSelect value={action.value} onChange={onChange} />;\n case 'SET_STATUS':\n return (\n <select\n className={fieldClass}\n value={action.value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">Choose a status</option>\n {WORK_ITEM_STATUSES.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n );\n case 'SET_PRIORITY':\n return (\n <select\n className={fieldClass}\n value={action.value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">Choose a priority</option>\n {WORK_ITEM_PRIORITIES.map((p) => (\n <option key={p} value={p}>\n {enumLabel(p)}\n </option>\n ))}\n </select>\n );\n case 'ADD_LABEL':\n default:\n return (\n <Input\n aria-label=\"Label\"\n className={inputTextClass}\n placeholder=\"e.g. needs-review\"\n value={action.value}\n onChange={(e) => onChange(e.target.value)}\n />\n );\n }\n}\n\n/** The actions builder: only the four types the engine can actually dispatch (see automationRuleTypes.ts). */\nexport function ActionsEditor({\n actions,\n onChange,\n}: {\n actions: RuleAction[];\n onChange: (next: RuleAction[]) => void;\n}) {\n const update = (index: number, patch: Partial<RuleAction>) =>\n onChange(actions.map((a, i) => (i === index ? { ...a, ...patch } : a)));\n const remove = (index: number) => onChange(actions.filter((_, i) => i !== index));\n const add = () => onChange([...actions, { type: 'ASSIGN_TO_USER', value: '' }]);\n\n return (\n <div className=\"space-y-2\">\n {actions.map((action, index) => {\n // A rule created before this UI existed (or via the API directly) can carry an action type this\n // builder does not offer (e.g. POST_COMMENT, which the engine never dispatches — see\n // automationRuleTypes.ts). Keep it selectable and visible rather than silently swapping it out from\n // under the user the moment they open the edit page.\n const known = (ACTION_TYPES as readonly string[]).includes(action.type);\n const typeOptions = known ? ACTION_TYPES : [action.type, ...ACTION_TYPES];\n return (\n <div key={index} className=\"space-y-1\">\n <div className=\"flex items-start gap-2\">\n <select\n aria-label=\"Action type\"\n className={`${fieldClass} mt-0 w-40 shrink-0`}\n value={action.type}\n onChange={(e) =>\n update(index, {\n type: e.target.value as ActionType,\n value: e.target.value === action.type ? action.value : '',\n })\n }\n >\n {typeOptions.map((t) => (\n <option key={t} value={t}>\n {known || t !== action.type ? (ACTION_TYPE_LABEL[t as ActionType] ?? t) : t}\n </option>\n ))}\n </select>\n <div className=\"min-w-0 flex-1\">\n <ActionValueEditor action={action} onChange={(value) => update(index, { value })} />\n </div>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n aria-label=\"Remove action\"\n onClick={() => remove(index)}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n {!known && (\n <p className=\"text-xs text-muted-foreground\">\n This action type is not run by the engine yet; the rule keeps it but never\n dispatches it.\n </p>\n )}\n </div>\n );\n })}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={add}\n data-testid=\"automation-add-action\"\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n Add action\n </Button>\n {actions.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">\n A rule with no actions still records that it ran, but changes nothing.\n </p>\n )}\n </div>\n );\n}\n\n/** The conditions builder. A rule with no conditions always matches — the gate is opt-in (engine default). */\nexport function ConditionsEditor({\n conditions,\n onChange,\n}: {\n conditions: RuleCondition[];\n onChange: (next: RuleCondition[]) => void;\n}) {\n const update = (index: number, patch: Partial<RuleCondition>) =>\n onChange(conditions.map((c, i) => (i === index ? { ...c, ...patch } : c)));\n const remove = (index: number) => onChange(conditions.filter((_, i) => i !== index));\n const add = () => onChange([...conditions, { field: 'status', operator: 'eq', value: '' }]);\n\n return (\n <div className=\"space-y-2\">\n {conditions.map((condition, index) => (\n <div key={index} className=\"flex items-start gap-2\">\n <Input\n aria-label=\"Condition field\"\n list=\"automation-condition-fields\"\n className={`w-36 shrink-0 ${inputTextClass}`}\n placeholder=\"field\"\n value={condition.field}\n onChange={(e) => update(index, { field: e.target.value })}\n />\n <select\n aria-label=\"Condition operator\"\n className={`${fieldClass} mt-0 w-32 shrink-0`}\n value={condition.operator}\n onChange={(e) => update(index, { operator: e.target.value as ConditionOperator })}\n >\n {CONDITION_OPERATORS.map((op) => (\n <option key={op} value={op}>\n {CONDITION_OPERATOR_LABEL[op]}\n </option>\n ))}\n </select>\n {condition.operator !== 'exists' && (\n <Input\n aria-label=\"Condition value\"\n className={`min-w-0 flex-1 ${inputTextClass}`}\n placeholder={\n condition.operator === 'in' || condition.operator === 'notIn'\n ? 'comma, separated, values'\n : 'value'\n }\n value={condition.value}\n onChange={(e) => update(index, { value: e.target.value })}\n />\n )}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n aria-label=\"Remove condition\"\n onClick={() => remove(index)}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n ))}\n <datalist id=\"automation-condition-fields\">\n {CONDITION_FIELDS.map((f) => (\n <option key={f} value={f} />\n ))}\n </datalist>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={add}\n data-testid=\"automation-add-condition\"\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n Add condition\n </Button>\n {conditions.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">\n No conditions: the rule matches every event.\n </p>\n )}\n </div>\n );\n}\n\n/** Name, description, project, trigger, conditions and actions — the fields create and edit share. */\nexport function AutomationRuleFields({\n form,\n onChange,\n projectLocked = false,\n}: {\n form: AutomationRuleFormState;\n onChange: (next: AutomationRuleFormState) => void;\n /** The edit page cannot change the project (the API accepts no `projectId` on update). */\n projectLocked?: boolean;\n}) {\n return (\n <div className=\"space-y-4\">\n <div>\n <label htmlFor=\"automation-name\" className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id=\"automation-name\"\n className={`mt-1 ${inputTextClass}`}\n value={form.name}\n onChange={(e) => onChange({ ...form, name: e.target.value })}\n placeholder=\"e.g. Assign new bugs to triage\"\n />\n </div>\n <div>\n <label htmlFor=\"automation-description\" className=\"text-sm font-medium\">\n Description\n </label>\n <textarea\n id=\"automation-description\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]\"\n value={form.description}\n onChange={(e) => onChange({ ...form, description: e.target.value })}\n placeholder=\"What this rule is for\"\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"automation-project\" className=\"text-sm font-medium\">\n Project\n </label>\n {projectLocked ? (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n A rule's project cannot be changed after it is created.\n </p>\n ) : (\n <div className=\"mt-1\">\n <ProjectSelect\n id=\"automation-project\"\n value={form.projectId}\n onChange={(projectId) => onChange({ ...form, projectId })}\n />\n </div>\n )}\n </div>\n <div>\n <label htmlFor=\"automation-trigger\" className=\"text-sm font-medium\">\n Trigger\n </label>\n <select\n id=\"automation-trigger\"\n className={fieldClass}\n value={form.triggerType}\n onChange={(e) => onChange({ ...form, triggerType: e.target.value as TriggerType })}\n >\n {TRIGGER_TYPES.map((t) => (\n <option key={t} value={t}>\n {TRIGGER_TYPE_LABEL[t]}\n {AUTOMATIC_TRIGGER_TYPES.has(t) ? '' : ' (not yet automatic)'}\n </option>\n ))}\n </select>\n {!AUTOMATIC_TRIGGER_TYPES.has(form.triggerType) && (\n <p className=\"mt-1 text-xs text-muted-foreground\">\n This trigger is saved but does not fire on its own yet — use{' '}\n <span className=\"font-medium\">Run now</span> to run it against a work item.\n </p>\n )}\n </div>\n </div>\n <div>\n <p className=\"text-sm font-medium\">Conditions</p>\n <p className=\"mb-2 text-xs text-muted-foreground\">\n All conditions must match. Leave empty to match every event.\n </p>\n <ConditionsEditor\n conditions={form.conditions}\n onChange={(conditions) => onChange({ ...form, conditions })}\n />\n </div>\n <div>\n <p className=\"text-sm font-medium\">Actions</p>\n <p className=\"mb-2 text-xs text-muted-foreground\">\n Run in order when the conditions match.\n </p>\n <ActionsEditor\n actions={form.actions}\n onChange={(actions) => onChange({ ...form, actions })}\n />\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAmCA,IAAM,IACJ,kGASI,IAAiB;AAEvB,SAAS,EAAkB,EACzB,WACA,eAIC;AACD,SAAQ,EAAO,MAAf;EACE,KAAK,iBACH,QAAO,kBAAC,GAAD;GAAgB,OAAO,EAAO;GAAiB;GAAY,CAAA;EACpE,KAAK,aACH,QACE,kBAAC,UAAD;GACE,WAAW;GACX,OAAO,EAAO;GACd,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;aAH3C,CAKE,kBAAC,UAAD;IAAQ,OAAM;cAAG;IAAwB,CAAA,EACxC,EAAmB,KAAK,MACvB,kBAAC,UAAD;IAAgB,OAAO;cACpB,EAAU,EAAE;IACN,EAFI,EAEJ,CACT,CACK;;EAEb,KAAK,eACH,QACE,kBAAC,UAAD;GACE,WAAW;GACX,OAAO,EAAO;GACd,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;aAH3C,CAKE,kBAAC,UAAD;IAAQ,OAAM;cAAG;IAA0B,CAAA,EAC1C,EAAqB,KAAK,MACzB,kBAAC,UAAD;IAAgB,OAAO;cACpB,EAAU,EAAE;IACN,EAFI,EAEJ,CACT,CACK;;EAGb,QACE,QACE,kBAAC,GAAD;GACE,cAAW;GACX,WAAW;GACX,aAAY;GACZ,OAAO,EAAO;GACd,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;GACzC,CAAA;;;AAMV,SAAgB,EAAc,EAC5B,YACA,eAIC;CACD,IAAM,KAAU,GAAe,MAC7B,EAAS,EAAQ,KAAK,GAAG,MAAO,MAAM,IAAQ;EAAE,GAAG;EAAG,GAAG;EAAO,GAAG,EAAG,CAAC,EACnE,KAAU,MAAkB,EAAS,EAAQ,QAAQ,GAAG,MAAM,MAAM,EAAM,CAAC;AAGjF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,EAAQ,KAAK,GAAQ,MAAU;IAK9B,IAAM,IAAS,EAAmC,SAAS,EAAO,KAAK,EACjE,IAAc,IAAQ,IAAe,CAAC,EAAO,MAAM,GAAG,EAAa;AACzE,WACE,kBAAC,OAAD;KAAiB,WAAU;eAA3B,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,UAAD;QACE,cAAW;QACX,WAAW,GAAG,EAAW;QACzB,OAAO,EAAO;QACd,WAAW,MACT,EAAO,GAAO;SACZ,MAAM,EAAE,OAAO;SACf,OAAO,EAAE,OAAO,UAAU,EAAO,OAAO,EAAO,QAAQ;SACxD,CAAC;kBAGH,EAAY,KAAK,MAChB,kBAAC,UAAD;SAAgB,OAAO;mBACpB,KAAS,MAAM,EAAO,OAAQ,EAAkB,MAAoB,IAAK;SACnE,EAFI,EAEJ,CACT;QACK,CAAA;OACT,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD;SAA2B;SAAQ,WAAW,MAAU,EAAO,GAAO,EAAE,UAAO,CAAC;SAAI,CAAA;QAChF,CAAA;OACN,kBAAC,GAAD;QACE,MAAK;QACL,MAAK;QACL,SAAQ;QACR,cAAW;QACX,eAAe,EAAO,EAAM;kBAE5B,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;QAClB,CAAA;OACL;SACL,CAAC,KACA,kBAAC,KAAD;MAAG,WAAU;gBAAgC;MAGzC,CAAA,CAEF;OAtCI,EAsCJ;KAER;GACF,kBAAC,GAAD;IACE,MAAK;IACL,MAAK;IACL,SAAQ;IACR,eAzDY,EAAS,CAAC,GAAG,GAAS;KAAE,MAAM;KAAkB,OAAO;KAAI,CAAC,CAAC;IA0DzE,eAAY;cALd,CAOE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,aAE1B;;GACR,EAAQ,WAAW,KAClB,kBAAC,KAAD;IAAG,WAAU;cAAgC;IAEzC,CAAA;GAEF;;;AAKV,SAAgB,EAAiB,EAC/B,eACA,eAIC;CACD,IAAM,KAAU,GAAe,MAC7B,EAAS,EAAW,KAAK,GAAG,MAAO,MAAM,IAAQ;EAAE,GAAG;EAAG,GAAG;EAAO,GAAG,EAAG,CAAC,EACtE,KAAU,MAAkB,EAAS,EAAW,QAAQ,GAAG,MAAM,MAAM,EAAM,CAAC;AAGpF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,EAAW,KAAK,GAAW,MAC1B,kBAAC,OAAD;IAAiB,WAAU;cAA3B;KACE,kBAAC,GAAD;MACE,cAAW;MACX,MAAK;MACL,WAAW,iBAAiB;MAC5B,aAAY;MACZ,OAAO,EAAU;MACjB,WAAW,MAAM,EAAO,GAAO,EAAE,OAAO,EAAE,OAAO,OAAO,CAAC;MACzD,CAAA;KACF,kBAAC,UAAD;MACE,cAAW;MACX,WAAW,GAAG,EAAW;MACzB,OAAO,EAAU;MACjB,WAAW,MAAM,EAAO,GAAO,EAAE,UAAU,EAAE,OAAO,OAA4B,CAAC;gBAEhF,EAAoB,KAAK,MACxB,kBAAC,UAAD;OAAiB,OAAO;iBACrB,EAAyB;OACnB,EAFI,EAEJ,CACT;MACK,CAAA;KACR,EAAU,aAAa,YACtB,kBAAC,GAAD;MACE,cAAW;MACX,WAAW,kBAAkB;MAC7B,aACE,EAAU,aAAa,QAAQ,EAAU,aAAa,UAClD,6BACA;MAEN,OAAO,EAAU;MACjB,WAAW,MAAM,EAAO,GAAO,EAAE,OAAO,EAAE,OAAO,OAAO,CAAC;MACzD,CAAA;KAEJ,kBAAC,GAAD;MACE,MAAK;MACL,MAAK;MACL,SAAQ;MACR,cAAW;MACX,eAAe,EAAO,EAAM;gBAE5B,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;MAClB,CAAA;KACL;MA3CI,EA2CJ,CACN;GACF,kBAAC,YAAD;IAAU,IAAG;cACV,EAAiB,KAAK,MACrB,kBAAC,UAAD,EAAgB,OAAO,GAAK,EAAf,EAAe,CAC5B;IACO,CAAA;GACX,kBAAC,GAAD;IACE,MAAK;IACL,MAAK;IACL,SAAQ;IACR,eA3DY,EAAS,CAAC,GAAG,GAAY;KAAE,OAAO;KAAU,UAAU;KAAM,OAAO;KAAI,CAAC,CAAC;IA4DrF,eAAY;cALd,CAOE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,gBAE1B;;GACR,EAAW,WAAW,KACrB,kBAAC,KAAD;IAAG,WAAU;cAAgC;IAEzC,CAAA;GAEF;;;AAKV,SAAgB,EAAqB,EACnC,SACA,aACA,mBAAgB,MAMf;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IAAO,SAAQ;IAAkB,WAAU;cAAsB;IAEzD,CAAA,EACR,kBAAC,GAAD;IACE,IAAG;IACH,WAAW,QAAQ;IACnB,OAAO,EAAK;IACZ,WAAW,MAAM,EAAS;KAAE,GAAG;KAAM,MAAM,EAAE,OAAO;KAAO,CAAC;IAC5D,aAAY;IACZ,CAAA,CACE,EAAA,CAAA;GACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IAAO,SAAQ;IAAyB,WAAU;cAAsB;IAEhE,CAAA,EACR,kBAAC,YAAD;IACE,IAAG;IACH,WAAU;IACV,OAAO,EAAK;IACZ,WAAW,MAAM,EAAS;KAAE,GAAG;KAAM,aAAa,EAAE,OAAO;KAAO,CAAC;IACnE,aAAY;IACZ,CAAA,CACE,EAAA,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAqB,WAAU;eAAsB;KAE5D,CAAA,EACP,IACC,kBAAC,KAAD;KAAG,WAAU;eAAqC;KAE9C,CAAA,GAEJ,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,IAAG;MACH,OAAO,EAAK;MACZ,WAAW,MAAc,EAAS;OAAE,GAAG;OAAM;OAAW,CAAC;MACzD,CAAA;KACE,CAAA,CAEJ,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,SAAD;MAAO,SAAQ;MAAqB,WAAU;gBAAsB;MAE5D,CAAA;KACR,kBAAC,UAAD;MACE,IAAG;MACH,WAAW;MACX,OAAO,EAAK;MACZ,WAAW,MAAM,EAAS;OAAE,GAAG;OAAM,aAAa,EAAE,OAAO;OAAsB,CAAC;gBAEjF,EAAc,KAAK,MAClB,kBAAC,UAAD;OAAgB,OAAO;iBAAvB,CACG,EAAmB,IACnB,EAAwB,IAAI,EAAE,GAAG,KAAK,uBAChC;SAHI,EAGJ,CACT;MACK,CAAA;KACR,CAAC,EAAwB,IAAI,EAAK,YAAY,IAC7C,kBAAC,KAAD;MAAG,WAAU;gBAAb;OAAkD;OACa;OAC7D,kBAAC,QAAD;QAAM,WAAU;kBAAc;QAAc,CAAA;;OAC1C;;KAEF,EAAA,CAAA,CACF;;GACN,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,KAAD;KAAG,WAAU;eAAsB;KAAc,CAAA;IACjD,kBAAC,KAAD;KAAG,WAAU;eAAqC;KAE9C,CAAA;IACJ,kBAAC,GAAD;KACE,YAAY,EAAK;KACjB,WAAW,MAAe,EAAS;MAAE,GAAG;MAAM;MAAY,CAAC;KAC3D,CAAA;IACE,EAAA,CAAA;GACN,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,KAAD;KAAG,WAAU;eAAsB;KAAW,CAAA;IAC9C,kBAAC,KAAD;KAAG,WAAU;eAAqC;KAE9C,CAAA;IACJ,kBAAC,GAAD;KACE,SAAS,EAAK;KACd,WAAW,MAAY,EAAS;MAAE,GAAG;MAAM;MAAS,CAAC;KACrD,CAAA;IACE,EAAA,CAAA;GACF"}
|
|
@@ -38,6 +38,13 @@ export declare function ProgramSelect({ id, value, onChange, noneLabel, }: {
|
|
|
38
38
|
onChange: (programId: string) => void;
|
|
39
39
|
noneLabel?: string;
|
|
40
40
|
}): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
/** A portfolio select (BOFF-8047). '' means "no portfolio" when `noneLabel` is given. */
|
|
42
|
+
export declare function PortfolioSelect({ id, value, onChange, noneLabel, }: {
|
|
43
|
+
id?: string;
|
|
44
|
+
value: string;
|
|
45
|
+
onChange: (portfolioId: string) => void;
|
|
46
|
+
noneLabel?: string;
|
|
47
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
41
48
|
/** Search, then pick one competitor. `exclude` hides competitors already linked. */
|
|
42
49
|
export declare function CompetitorSearchSelect({ id, value, onChange, exclude, }: {
|
|
43
50
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pickers.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/Pickers.tsx"],"names":[],"mappings":"AA2BA,iHAAiH;AACjH,wBAAgB,aAAa,CAAC,EAC5B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,EACT,OAAY,GACb,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CA8BA;AAcD,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,EACzB,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA+BA;AAeD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,EAAE,EACF,SAAS,EACT,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CAqDA;AAcD,iCAAiC;AACjC,wBAAgB,eAAe,CAAC,EAC9B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CA8CA;AAcD,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,EAC5B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA8BA;AAcD,oFAAoF;AACpF,wBAAgB,sBAAsB,CAAC,EACrC,EAAE,EACF,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CAkDA"}
|
|
1
|
+
{"version":3,"file":"Pickers.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/controls/Pickers.tsx"],"names":[],"mappings":"AA2BA,iHAAiH;AACjH,wBAAgB,aAAa,CAAC,EAC5B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,EACT,OAAY,GACb,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CA8BA;AAcD,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,EACzB,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA+BA;AAeD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,EAAE,EACF,SAAS,EACT,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CAqDA;AAcD,iCAAiC;AACjC,wBAAgB,eAAe,CAAC,EAC9B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CA8CA;AAcD,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,EAC5B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA8BA;AAcD,yFAAyF;AACzF,wBAAgB,eAAe,CAAC,EAC9B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,2CA8BA;AAcD,oFAAoF;AACpF,wBAAgB,sBAAsB,CAAC,EACrC,EAAE,EACF,KAAK,EACL,QAAQ,EACR,OAAY,GACb,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,2CAkDA"}
|
|
@@ -241,6 +241,43 @@ function v({ id: t, value: n, onChange: r, noneLabel: s }) {
|
|
|
241
241
|
});
|
|
242
242
|
}
|
|
243
243
|
var y = r`
|
|
244
|
+
query PickerPortfolios($workspaceId: ID!) {
|
|
245
|
+
planmagnetPortfolios(workspaceId: $workspaceId, pagination: { first: 100 }) {
|
|
246
|
+
items {
|
|
247
|
+
id
|
|
248
|
+
name
|
|
249
|
+
}
|
|
250
|
+
totalCount
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
function b({ id: t, value: n, onChange: r, noneLabel: s }) {
|
|
255
|
+
let { workspaceId: l } = e(), { data: u, loading: d, error: f } = i(y, {
|
|
256
|
+
variables: { workspaceId: l ?? "" },
|
|
257
|
+
skip: !l
|
|
258
|
+
}), p = u?.planmagnetPortfolios.items ?? [];
|
|
259
|
+
return /* @__PURE__ */ o("select", {
|
|
260
|
+
id: t,
|
|
261
|
+
className: c,
|
|
262
|
+
value: n,
|
|
263
|
+
onChange: (e) => r(e.target.value),
|
|
264
|
+
children: [
|
|
265
|
+
s !== void 0 && /* @__PURE__ */ a("option", {
|
|
266
|
+
value: "",
|
|
267
|
+
children: s
|
|
268
|
+
}),
|
|
269
|
+
p.length === 0 && s === void 0 && /* @__PURE__ */ a("option", {
|
|
270
|
+
value: "",
|
|
271
|
+
children: d && !u ? "Loading portfolios…" : f ? "Portfolios could not be loaded" : "No portfolios yet"
|
|
272
|
+
}),
|
|
273
|
+
p.map((e) => /* @__PURE__ */ a("option", {
|
|
274
|
+
value: e.id,
|
|
275
|
+
children: e.name
|
|
276
|
+
}, e.id))
|
|
277
|
+
]
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
var x = r`
|
|
244
281
|
query PickerCompetitors($workspaceId: ID!, $filter: PlanmagnetCompetitorFilter) {
|
|
245
282
|
planmagnetCompetitors(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {
|
|
246
283
|
items {
|
|
@@ -251,14 +288,14 @@ var y = r`
|
|
|
251
288
|
}
|
|
252
289
|
}
|
|
253
290
|
`;
|
|
254
|
-
function
|
|
255
|
-
let { workspaceId: f } = e(), [p, m] = n(""), h = t(p.trim()), { data: g, loading: _ } = i(
|
|
291
|
+
function S({ id: r, value: l, onChange: u, exclude: d = [] }) {
|
|
292
|
+
let { workspaceId: f } = e(), [p, m] = n(""), h = t(p.trim()), { data: g, loading: _ } = i(x, {
|
|
256
293
|
variables: {
|
|
257
294
|
workspaceId: f ?? "",
|
|
258
295
|
filter: h ? { search: h } : void 0
|
|
259
296
|
},
|
|
260
297
|
skip: !f
|
|
261
|
-
}), v = g?.planmagnetCompetitors.items ?? [],
|
|
298
|
+
}), v = g?.planmagnetCompetitors.items ?? [], y = v.filter((e) => !d.includes(e.id)), b = g?.planmagnetCompetitors.totalCount ?? 0;
|
|
262
299
|
return /* @__PURE__ */ o("div", {
|
|
263
300
|
className: "space-y-1",
|
|
264
301
|
children: [
|
|
@@ -275,19 +312,19 @@ function b({ id: r, value: l, onChange: u, exclude: d = [] }) {
|
|
|
275
312
|
onChange: (e) => u(e.target.value),
|
|
276
313
|
children: [/* @__PURE__ */ a("option", {
|
|
277
314
|
value: "",
|
|
278
|
-
children: _ && !g ? "Searching…" :
|
|
279
|
-
}),
|
|
315
|
+
children: _ && !g ? "Searching…" : y.length ? "Choose a competitor" : "No match"
|
|
316
|
+
}), y.map((e) => /* @__PURE__ */ a("option", {
|
|
280
317
|
value: e.id,
|
|
281
318
|
children: e.name
|
|
282
319
|
}, e.id))]
|
|
283
320
|
}),
|
|
284
|
-
|
|
321
|
+
b > v.length && /* @__PURE__ */ o("p", {
|
|
285
322
|
className: "text-xs text-muted-foreground",
|
|
286
323
|
children: [
|
|
287
324
|
"Showing ",
|
|
288
325
|
v.length,
|
|
289
326
|
" of ",
|
|
290
|
-
|
|
327
|
+
b,
|
|
291
328
|
" — type to narrow the list."
|
|
292
329
|
]
|
|
293
330
|
})
|
|
@@ -295,6 +332,6 @@ function b({ id: r, value: l, onChange: u, exclude: d = [] }) {
|
|
|
295
332
|
});
|
|
296
333
|
}
|
|
297
334
|
//#endregion
|
|
298
|
-
export {
|
|
335
|
+
export { S as CompetitorSearchSelect, g as DocSearchSelect, b as PortfolioSelect, v as ProgramSelect, u as ProjectSelect, f as TeamSelect, m as WorkItemSearchSelect };
|
|
299
336
|
|
|
300
337
|
//# sourceMappingURL=Pickers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pickers.js","names":[],"sources":["../../../src/planmagnet/controls/Pickers.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\n/**\n * Record pickers shared by forms and dialogs: a project select, and search-then-pick selects for work items and\n * docs. Values are record ids. Search runs on the server (never \"the first page, filtered in the browser\"), so a\n * record past the first page can still be found.\n */\n\nconst selectClass =\n 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\nconst PICKER_PROJECTS = gql`\n query PickerProjects($workspaceId: ID!) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A project select. '' means \"no project\" when `noneLabel` is given. `exclude` hides ids already spoken for. */\nexport function ProjectSelect({\n id,\n value,\n onChange,\n noneLabel,\n exclude = [],\n}: {\n id?: string;\n value: string;\n onChange: (projectId: string) => void;\n noneLabel?: string;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetProjects: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROJECTS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const projects = (data?.planmagnetProjects.items ?? []).filter((p) => !exclude.includes(p.id));\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {projects.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading projects…'\n : error\n ? 'Projects could not be loaded'\n : 'No projects yet'}\n </option>\n )}\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_TEAMS = gql`\n query PickerTeams($workspaceId: ID!) {\n planmagnetTeams(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A team select (BOFF-8044). '' means \"no team\" when `noneLabel` is given. */\nexport function TeamSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (teamId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetTeams: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_TEAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const teams = data?.planmagnetTeams.items ?? [];\n return (\n <select\n id={id}\n aria-label={id ? undefined : 'Team'}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {teams.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading teams…'\n : error\n ? 'Teams could not be loaded'\n : 'No teams yet'}\n </option>\n )}\n {teams.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_WORK_ITEMS = gql`\n query PickerWorkItems($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n key\n title\n }\n totalCount\n }\n }\n`;\n\n/**\n * Search, then pick one work item. The server matches the key, title and description. `exclude` hides items that are\n * already linked (the result says how many more exist, so an empty list never claims \"nothing left\" falsely).\n */\nexport function WorkItemSearchSelect({\n id,\n projectId,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n projectId?: string;\n value: string;\n onChange: (workItemId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetWorkItems: {\n items: { id: string; key: string; title: string }[];\n totalCount: number;\n };\n }>(PICKER_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: { ...(deferred && { search: deferred }), ...(projectId && { projectId }) },\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetWorkItems.items ?? [];\n const items = found.filter((w) => !exclude.includes(w.id));\n const total = data?.planmagnetWorkItems.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search work items\"\n placeholder=\"Search by key or title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : items.length ? 'Choose a work item' : 'No match'}\n </option>\n {/* The current value stays selectable (and visible) when it is not in this page of results. */}\n {value && !items.some((w) => w.id === value) && (\n <option value={value}>Currently linked work item</option>\n )}\n {items.map((w) => (\n <option key={w.id} value={w.id}>\n {w.key} — {w.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_DOCS = gql`\n query PickerDocs($workspaceId: String, $filter: PlanmagnetDocFilter) {\n planmagnetDocs(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n title\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one doc. */\nexport function DocSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (docId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetDocs: { items: { id: string; title: string }[]; totalCount: number };\n }>(PICKER_DOCS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetDocs.items ?? [];\n const docs = found.filter((d) => !exclude.includes(d.id));\n const total = data?.planmagnetDocs.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search docs\"\n placeholder=\"Search docs by title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : docs.length ? 'Choose a doc' : 'No match'}\n </option>\n {docs.map((d) => (\n <option key={d.id} value={d.id}>\n {d.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_PROGRAMS = gql`\n query PickerPrograms($workspaceId: ID!) {\n planmagnetPrograms(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A program select. '' means \"no program\" when `noneLabel` is given. */\nexport function ProgramSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (programId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetPrograms: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROGRAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const programs = data?.planmagnetPrograms.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {programs.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading programs…'\n : error\n ? 'Programs could not be loaded'\n : 'No programs yet'}\n </option>\n )}\n {programs.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_COMPETITORS = gql`\n query PickerCompetitors($workspaceId: ID!, $filter: PlanmagnetCompetitorFilter) {\n planmagnetCompetitors(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one competitor. `exclude` hides competitors already linked. */\nexport function CompetitorSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (competitorId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetCompetitors: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_COMPETITORS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetCompetitors.items ?? [];\n const competitors = found.filter((c) => !exclude.includes(c.id));\n const total = data?.planmagnetCompetitors.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search competitors\"\n placeholder=\"Search competitors by name\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data\n ? 'Searching…'\n : competitors.length\n ? 'Choose a competitor'\n : 'No match'}\n </option>\n {competitors.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAYA,IAAM,IACJ,6FAEI,IAAkB,CAAG;;;;;;;;;;;AAa3B,SAAgB,EAAc,EAC5B,OACA,UACA,aACA,cACA,aAAU,EAAE,IAOX;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,EAAE,SAAM,YAAS,aAAU,EAE9B,GAAiB;EAAE,WAAW,EAAE,aAAa,KAAe,IAAI;EAAE,MAAM,CAAC;EAAa,CAAC,EACpF,KAAY,GAAM,mBAAmB,SAAS,EAAE,EAAE,QAAQ,MAAM,CAAC,EAAQ,SAAS,EAAE,GAAG,CAAC;AAC9F,QACE,kBAAC,UAAD;EACM;EACJ,WAAW;EACJ;EACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;YAJ3C;GAMG,MAAc,KAAA,KAAa,kBAAC,UAAD;IAAQ,OAAM;cAAI;IAAmB,CAAA;GAChE,EAAS,WAAW,KAAK,MAAc,KAAA,KACtC,kBAAC,UAAD;IAAQ,OAAM;cACX,KAAW,CAAC,IACT,sBACA,IACE,iCACA;IACC,CAAA;GAEV,EAAS,KAAK,MACb,kBAAC,UAAD;IAAmB,OAAO,EAAE;cACzB,EAAE;IACI,EAFI,EAAE,GAEN,CACT;GACK;;;AAIb,IAAM,IAAe,CAAG;;;;;;;;;;;AAaxB,SAAgB,EAAW,EACzB,OACA,UACA,aACA,gBAMC;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,EAAE,SAAM,YAAS,aAAU,EAE9B,GAAc;EAAE,WAAW,EAAE,aAAa,KAAe,IAAI;EAAE,MAAM,CAAC;EAAa,CAAC,EACjF,IAAQ,GAAM,gBAAgB,SAAS,EAAE;AAC/C,QACE,kBAAC,UAAD;EACM;EACJ,cAAY,IAAK,KAAA,IAAY;EAC7B,WAAW;EACJ;EACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;YAL3C;GAOG,MAAc,KAAA,KAAa,kBAAC,UAAD;IAAQ,OAAM;cAAI;IAAmB,CAAA;GAChE,EAAM,WAAW,KAAK,MAAc,KAAA,KACnC,kBAAC,UAAD;IAAQ,OAAM;cACX,KAAW,CAAC,IACT,mBACA,IACE,8BACA;IACC,CAAA;GAEV,EAAM,KAAK,MACV,kBAAC,UAAD;IAAmB,OAAO,EAAE;cACzB,EAAE;IACI,EAFI,EAAE,GAEN,CACT;GACK;;;AAIb,IAAM,IAAoB,CAAG;;;;;;;;;;;;AAiB7B,SAAgB,EAAqB,EACnC,OACA,cACA,UACA,aACA,aAAU,EAAE,IAOX;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,IAAW,EAAiB,EAAO,MAAM,CAAC,EAC1C,EAAE,SAAM,eAAY,EAKvB,GAAmB;EACpB,WAAW;GACT,aAAa,KAAe;GAC5B,QAAQ;IAAE,GAAI,KAAY,EAAE,QAAQ,GAAU;IAAG,GAAI,KAAa,EAAE,cAAW;IAAG;GACnF;EACD,MAAM,CAAC;EACR,CAAC,EACI,IAAQ,GAAM,oBAAoB,SAAS,EAAE,EAC7C,IAAQ,EAAM,QAAQ,MAAM,CAAC,EAAQ,SAAS,EAAE,GAAG,CAAC,EACpD,IAAQ,GAAM,oBAAoB,cAAc;AACtD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,cAAW;IACX,aAAY;IACZ,OAAO;IACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;IAC1C,CAAA;GACF,kBAAC,UAAD;IACM;IACJ,WAAW;IACJ;IACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;cAJ3C;KAME,kBAAC,UAAD;MAAQ,OAAM;gBACX,KAAW,CAAC,IAAO,eAAe,EAAM,SAAS,uBAAuB;MAClE,CAAA;KAER,KAAS,CAAC,EAAM,MAAM,MAAM,EAAE,OAAO,EAAM,IAC1C,kBAAC,UAAD;MAAe;gBAAO;MAAmC,CAAA;KAE1D,EAAM,KAAK,MACV,kBAAC,UAAD;MAAmB,OAAO,EAAE;gBAA5B;OACG,EAAE;OAAI;OAAI,EAAE;OACN;QAFI,EAAE,GAEN,CACT;KACK;;GACR,IAAQ,EAAM,UACb,kBAAC,KAAD;IAAG,WAAU;cAAb;KAA6C;KAClC,EAAM;KAAO;KAAK;KAAM;KAC/B;;GAEF;;;AAIV,IAAM,IAAc,CAAG;;;;;;;;;;;AAavB,SAAgB,EAAgB,EAC9B,OACA,UACA,aACA,aAAU,EAAE,IAMX;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,IAAW,EAAiB,EAAO,MAAM,CAAC,EAC1C,EAAE,SAAM,eAAY,EAEvB,GAAa;EACd,WAAW;GACT,aAAa,KAAe;GAC5B,QAAQ,IAAW,EAAE,QAAQ,GAAU,GAAG,KAAA;GAC3C;EACD,MAAM,CAAC;EACR,CAAC,EACI,IAAQ,GAAM,eAAe,SAAS,EAAE,EACxC,IAAO,EAAM,QAAQ,MAAM,CAAC,EAAQ,SAAS,EAAE,GAAG,CAAC,EACnD,IAAQ,GAAM,eAAe,cAAc;AACjD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,cAAW;IACX,aAAY;IACZ,OAAO;IACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;IAC1C,CAAA;GACF,kBAAC,UAAD;IACM;IACJ,WAAW;IACJ;IACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;cAJ3C,CAME,kBAAC,UAAD;KAAQ,OAAM;eACX,KAAW,CAAC,IAAO,eAAe,EAAK,SAAS,iBAAiB;KAC3D,CAAA,EACR,EAAK,KAAK,MACT,kBAAC,UAAD;KAAmB,OAAO,EAAE;eACzB,EAAE;KACI,EAFI,EAAE,GAEN,CACT,CACK;;GACR,IAAQ,EAAM,UACb,kBAAC,KAAD;IAAG,WAAU;cAAb;KAA6C;KAClC,EAAM;KAAO;KAAK;KAAM;KAC/B;;GAEF;;;AAIV,IAAM,IAAkB,CAAG;;;;;;;;;;;AAa3B,SAAgB,EAAc,EAC5B,OACA,UACA,aACA,gBAMC;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,EAAE,SAAM,YAAS,aAAU,EAE9B,GAAiB;EAAE,WAAW,EAAE,aAAa,KAAe,IAAI;EAAE,MAAM,CAAC;EAAa,CAAC,EACpF,IAAW,GAAM,mBAAmB,SAAS,EAAE;AACrD,QACE,kBAAC,UAAD;EACM;EACJ,WAAW;EACJ;EACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;YAJ3C;GAMG,MAAc,KAAA,KAAa,kBAAC,UAAD;IAAQ,OAAM;cAAI;IAAmB,CAAA;GAChE,EAAS,WAAW,KAAK,MAAc,KAAA,KACtC,kBAAC,UAAD;IAAQ,OAAM;cACX,KAAW,CAAC,IACT,sBACA,IACE,iCACA;IACC,CAAA;GAEV,EAAS,KAAK,MACb,kBAAC,UAAD;IAAmB,OAAO,EAAE;cACzB,EAAE;IACI,EAFI,EAAE,GAEN,CACT;GACK;;;AAIb,IAAM,IAAqB,CAAG;;;;;;;;;;;AAa9B,SAAgB,EAAuB,EACrC,OACA,UACA,aACA,aAAU,EAAE,IAMX;CACD,IAAM,EAAE,mBAAgB,GAAe,EACjC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,IAAW,EAAiB,EAAO,MAAM,CAAC,EAC1C,EAAE,SAAM,eAAY,EAEvB,GAAoB;EACrB,WAAW;GACT,aAAa,KAAe;GAC5B,QAAQ,IAAW,EAAE,QAAQ,GAAU,GAAG,KAAA;GAC3C;EACD,MAAM,CAAC;EACR,CAAC,EACI,IAAQ,GAAM,sBAAsB,SAAS,EAAE,EAC/C,IAAc,EAAM,QAAQ,MAAM,CAAC,EAAQ,SAAS,EAAE,GAAG,CAAC,EAC1D,IAAQ,GAAM,sBAAsB,cAAc;AACxD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,cAAW;IACX,aAAY;IACZ,OAAO;IACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;IAC1C,CAAA;GACF,kBAAC,UAAD;IACM;IACJ,WAAW;IACJ;IACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;cAJ3C,CAME,kBAAC,UAAD;KAAQ,OAAM;eACX,KAAW,CAAC,IACT,eACA,EAAY,SACV,wBACA;KACC,CAAA,EACR,EAAY,KAAK,MAChB,kBAAC,UAAD;KAAmB,OAAO,EAAE;eACzB,EAAE;KACI,EAFI,EAAE,GAEN,CACT,CACK;;GACR,IAAQ,EAAM,UACb,kBAAC,KAAD;IAAG,WAAU;cAAb;KAA6C;KAClC,EAAM;KAAO;KAAK;KAAM;KAC/B;;GAEF"}
|
|
1
|
+
{"version":3,"file":"Pickers.js","names":[],"sources":["../../../src/planmagnet/controls/Pickers.tsx"],"sourcesContent":["import { useDeferredValue, useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\n\n/**\n * Record pickers shared by forms and dialogs: a project select, and search-then-pick selects for work items and\n * docs. Values are record ids. Search runs on the server (never \"the first page, filtered in the browser\"), so a\n * record past the first page can still be found.\n */\n\nconst selectClass =\n 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\nconst PICKER_PROJECTS = gql`\n query PickerProjects($workspaceId: ID!) {\n planmagnetProjects(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A project select. '' means \"no project\" when `noneLabel` is given. `exclude` hides ids already spoken for. */\nexport function ProjectSelect({\n id,\n value,\n onChange,\n noneLabel,\n exclude = [],\n}: {\n id?: string;\n value: string;\n onChange: (projectId: string) => void;\n noneLabel?: string;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetProjects: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROJECTS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const projects = (data?.planmagnetProjects.items ?? []).filter((p) => !exclude.includes(p.id));\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {projects.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading projects…'\n : error\n ? 'Projects could not be loaded'\n : 'No projects yet'}\n </option>\n )}\n {projects.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_TEAMS = gql`\n query PickerTeams($workspaceId: ID!) {\n planmagnetTeams(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A team select (BOFF-8044). '' means \"no team\" when `noneLabel` is given. */\nexport function TeamSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (teamId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetTeams: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_TEAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const teams = data?.planmagnetTeams.items ?? [];\n return (\n <select\n id={id}\n aria-label={id ? undefined : 'Team'}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {teams.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading teams…'\n : error\n ? 'Teams could not be loaded'\n : 'No teams yet'}\n </option>\n )}\n {teams.map((t) => (\n <option key={t.id} value={t.id}>\n {t.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_WORK_ITEMS = gql`\n query PickerWorkItems($workspaceId: ID!, $filter: PlanmagnetWorkItemFilter) {\n planmagnetWorkItems(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n key\n title\n }\n totalCount\n }\n }\n`;\n\n/**\n * Search, then pick one work item. The server matches the key, title and description. `exclude` hides items that are\n * already linked (the result says how many more exist, so an empty list never claims \"nothing left\" falsely).\n */\nexport function WorkItemSearchSelect({\n id,\n projectId,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n projectId?: string;\n value: string;\n onChange: (workItemId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetWorkItems: {\n items: { id: string; key: string; title: string }[];\n totalCount: number;\n };\n }>(PICKER_WORK_ITEMS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: { ...(deferred && { search: deferred }), ...(projectId && { projectId }) },\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetWorkItems.items ?? [];\n const items = found.filter((w) => !exclude.includes(w.id));\n const total = data?.planmagnetWorkItems.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search work items\"\n placeholder=\"Search by key or title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : items.length ? 'Choose a work item' : 'No match'}\n </option>\n {/* The current value stays selectable (and visible) when it is not in this page of results. */}\n {value && !items.some((w) => w.id === value) && (\n <option value={value}>Currently linked work item</option>\n )}\n {items.map((w) => (\n <option key={w.id} value={w.id}>\n {w.key} — {w.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_DOCS = gql`\n query PickerDocs($workspaceId: String, $filter: PlanmagnetDocFilter) {\n planmagnetDocs(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n title\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one doc. */\nexport function DocSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (docId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetDocs: { items: { id: string; title: string }[]; totalCount: number };\n }>(PICKER_DOCS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetDocs.items ?? [];\n const docs = found.filter((d) => !exclude.includes(d.id));\n const total = data?.planmagnetDocs.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search docs\"\n placeholder=\"Search docs by title\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data ? 'Searching…' : docs.length ? 'Choose a doc' : 'No match'}\n </option>\n {docs.map((d) => (\n <option key={d.id} value={d.id}>\n {d.title}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n\nconst PICKER_PROGRAMS = gql`\n query PickerPrograms($workspaceId: ID!) {\n planmagnetPrograms(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A program select. '' means \"no program\" when `noneLabel` is given. */\nexport function ProgramSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (programId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetPrograms: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PROGRAMS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const programs = data?.planmagnetPrograms.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {programs.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading programs…'\n : error\n ? 'Programs could not be loaded'\n : 'No programs yet'}\n </option>\n )}\n {programs.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_PORTFOLIOS = gql`\n query PickerPortfolios($workspaceId: ID!) {\n planmagnetPortfolios(workspaceId: $workspaceId, pagination: { first: 100 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** A portfolio select (BOFF-8047). '' means \"no portfolio\" when `noneLabel` is given. */\nexport function PortfolioSelect({\n id,\n value,\n onChange,\n noneLabel,\n}: {\n id?: string;\n value: string;\n onChange: (portfolioId: string) => void;\n noneLabel?: string;\n}) {\n const { workspaceId } = usePlanMagnet();\n const { data, loading, error } = useQuery<{\n planmagnetPortfolios: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_PORTFOLIOS, { variables: { workspaceId: workspaceId ?? '' }, skip: !workspaceId });\n const portfolios = data?.planmagnetPortfolios.items ?? [];\n return (\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n {noneLabel !== undefined && <option value=\"\">{noneLabel}</option>}\n {portfolios.length === 0 && noneLabel === undefined && (\n <option value=\"\">\n {loading && !data\n ? 'Loading portfolios…'\n : error\n ? 'Portfolios could not be loaded'\n : 'No portfolios yet'}\n </option>\n )}\n {portfolios.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n );\n}\n\nconst PICKER_COMPETITORS = gql`\n query PickerCompetitors($workspaceId: ID!, $filter: PlanmagnetCompetitorFilter) {\n planmagnetCompetitors(workspaceId: $workspaceId, filter: $filter, pagination: { first: 20 }) {\n items {\n id\n name\n }\n totalCount\n }\n }\n`;\n\n/** Search, then pick one competitor. `exclude` hides competitors already linked. */\nexport function CompetitorSearchSelect({\n id,\n value,\n onChange,\n exclude = [],\n}: {\n id: string;\n value: string;\n onChange: (competitorId: string) => void;\n exclude?: string[];\n}) {\n const { workspaceId } = usePlanMagnet();\n const [search, setSearch] = useState('');\n const deferred = useDeferredValue(search.trim());\n const { data, loading } = useQuery<{\n planmagnetCompetitors: { items: { id: string; name: string }[]; totalCount: number };\n }>(PICKER_COMPETITORS, {\n variables: {\n workspaceId: workspaceId ?? '',\n filter: deferred ? { search: deferred } : undefined,\n },\n skip: !workspaceId,\n });\n const found = data?.planmagnetCompetitors.items ?? [];\n const competitors = found.filter((c) => !exclude.includes(c.id));\n const total = data?.planmagnetCompetitors.totalCount ?? 0;\n return (\n <div className=\"space-y-1\">\n <Input\n aria-label=\"Search competitors\"\n placeholder=\"Search competitors by name\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n <select\n id={id}\n className={selectClass}\n value={value}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">\n {loading && !data\n ? 'Searching…'\n : competitors.length\n ? 'Choose a competitor'\n : 'No match'}\n </option>\n {competitors.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n {total > found.length && (\n <p className=\"text-xs text-muted-foreground\">\n Showing {found.length} of {total} — type to narrow the list.\n </p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PortfoliosPortfolioDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,iCAAiC,
|
|
1
|
+
{"version":3,"file":"PortfoliosPortfolioDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/PortfoliosPortfolioDetailPagePage.tsx"],"names":[],"mappings":"AA6EA,wBAAgB,iCAAiC,4CAmRhD"}
|