@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
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BUDGET_STATE_STYLE as e, budgetState as t, budgetUsage as n, money as r } from "../../budgets/budgetMath.js";
|
|
2
|
+
import { Timer as i, Wallet as a } from "lucide-react";
|
|
2
3
|
import { gql as o } from "@apollo/client";
|
|
3
4
|
import { useQuery as s } from "@apollo/client/react";
|
|
4
5
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
@@ -153,81 +154,38 @@ function y({ workspaceId: e, projectId: t }) {
|
|
|
153
154
|
children: n("planmagnet.analytics.cycleTime.noProject", "Select a project to see its cycle time.")
|
|
154
155
|
}) })] });
|
|
155
156
|
}
|
|
156
|
-
function b(
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
fill: "bg-muted",
|
|
166
|
-
Icon: r,
|
|
167
|
-
text: "text-muted-foreground"
|
|
168
|
-
},
|
|
169
|
-
"on-track": {
|
|
170
|
-
track: "bg-primary/15",
|
|
171
|
-
fill: "bg-primary",
|
|
172
|
-
Icon: n,
|
|
173
|
-
text: "text-muted-foreground"
|
|
174
|
-
},
|
|
175
|
-
"near-limit": {
|
|
176
|
-
track: "bg-status-warning-subtle",
|
|
177
|
-
fill: "bg-status-warning",
|
|
178
|
-
Icon: t,
|
|
179
|
-
text: "text-status-warning-text"
|
|
180
|
-
},
|
|
181
|
-
"over-budget": {
|
|
182
|
-
track: "bg-status-error-subtle",
|
|
183
|
-
fill: "bg-status-error",
|
|
184
|
-
Icon: e,
|
|
185
|
-
text: "text-status-error-text"
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
function C(e, t) {
|
|
189
|
-
try {
|
|
190
|
-
return new Intl.NumberFormat(void 0, {
|
|
191
|
-
style: "currency",
|
|
192
|
-
currency: t,
|
|
193
|
-
maximumFractionDigits: 0
|
|
194
|
-
}).format(e);
|
|
195
|
-
} catch {
|
|
196
|
-
return `${Math.round(e).toLocaleString()} ${t}`;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
function w({ workspaceId: e }) {
|
|
200
|
-
let { t } = u(), { data: n, loading: r, error: i } = s(g, {
|
|
201
|
-
variables: { workspaceId: e },
|
|
202
|
-
skip: !e
|
|
203
|
-
}), o = [...n?.planmagnetBudgets?.items ?? []].sort((e, t) => (b(t.plannedAmount, t.spentAmount) ?? -1) - (b(e.plannedAmount, e.spentAmount) ?? -1)), h = n?.planmagnetBudgets?.totalCount ?? o.length, _ = {
|
|
204
|
-
"no-plan": t("planmagnet.analytics.budget.noPlan", "No plan set"),
|
|
205
|
-
"on-track": t("planmagnet.analytics.budget.onTrack", "On track"),
|
|
206
|
-
"near-limit": t("planmagnet.analytics.budget.nearLimit", "Near limit"),
|
|
207
|
-
"over-budget": t("planmagnet.analytics.budget.over", "Over budget")
|
|
157
|
+
function b({ workspaceId: i }) {
|
|
158
|
+
let { t: o } = u(), { data: h, loading: _, error: v } = s(g, {
|
|
159
|
+
variables: { workspaceId: i },
|
|
160
|
+
skip: !i
|
|
161
|
+
}), y = [...h?.planmagnetBudgets?.items ?? []].sort((e, t) => (n(t.plannedAmount, t.spentAmount) ?? -1) - (n(e.plannedAmount, e.spentAmount) ?? -1)), b = h?.planmagnetBudgets?.totalCount ?? y.length, x = {
|
|
162
|
+
"no-plan": o("planmagnet.analytics.budget.noPlan", "No plan set"),
|
|
163
|
+
"on-track": o("planmagnet.analytics.budget.onTrack", "On track"),
|
|
164
|
+
"near-limit": o("planmagnet.analytics.budget.nearLimit", "Near limit"),
|
|
165
|
+
"over-budget": o("planmagnet.analytics.budget.over", "Over budget")
|
|
208
166
|
};
|
|
209
167
|
return /* @__PURE__ */ l(d, { children: [/* @__PURE__ */ l(p, { children: [/* @__PURE__ */ l(m, {
|
|
210
168
|
className: "flex items-center gap-2",
|
|
211
|
-
children: [/* @__PURE__ */ c(a, { className: "h-4 w-4" }),
|
|
169
|
+
children: [/* @__PURE__ */ c(a, { className: "h-4 w-4" }), o("planmagnet.analytics.budget.title", "Budget performance")]
|
|
212
170
|
}), /* @__PURE__ */ c("p", {
|
|
213
171
|
className: "text-xs text-muted-foreground",
|
|
214
|
-
children:
|
|
215
|
-
shown:
|
|
216
|
-
total:
|
|
217
|
-
}) :
|
|
218
|
-
})] }), /* @__PURE__ */ c(f, { children:
|
|
172
|
+
children: b > y.length ? o("planmagnet.analytics.budget.subtitlePartial", "Spent against plan, per budget — the newest {{shown}} of {{total}}, riskiest first.", {
|
|
173
|
+
shown: y.length,
|
|
174
|
+
total: b
|
|
175
|
+
}) : o("planmagnet.analytics.budget.subtitle", "Spent against plan, per budget, across the workspace.")
|
|
176
|
+
})] }), /* @__PURE__ */ c(f, { children: _ ? /* @__PURE__ */ c("p", {
|
|
219
177
|
className: "text-sm text-muted-foreground",
|
|
220
|
-
children:
|
|
221
|
-
}) :
|
|
178
|
+
children: o("planmagnet.analytics.loading", "Loading…")
|
|
179
|
+
}) : v ? /* @__PURE__ */ l("p", {
|
|
222
180
|
className: "text-sm text-status-error-text",
|
|
223
181
|
children: [
|
|
224
|
-
|
|
182
|
+
o("planmagnet.analytics.budget.error", "Budgets could not be loaded"),
|
|
225
183
|
": ",
|
|
226
|
-
|
|
184
|
+
v.message
|
|
227
185
|
]
|
|
228
|
-
}) :
|
|
186
|
+
}) : y.length === 0 ? /* @__PURE__ */ c("p", {
|
|
229
187
|
className: "text-sm text-muted-foreground",
|
|
230
|
-
children:
|
|
188
|
+
children: o("planmagnet.analytics.budget.empty", "No budgets in this workspace yet.")
|
|
231
189
|
}) : /* @__PURE__ */ l("table", {
|
|
232
190
|
className: "w-full text-sm",
|
|
233
191
|
children: [/* @__PURE__ */ c("thead", { children: /* @__PURE__ */ l("tr", {
|
|
@@ -235,23 +193,23 @@ function w({ workspaceId: e }) {
|
|
|
235
193
|
children: [
|
|
236
194
|
/* @__PURE__ */ c("th", {
|
|
237
195
|
className: "py-1 pr-2 font-normal",
|
|
238
|
-
children:
|
|
196
|
+
children: o("planmagnet.analytics.budget.name", "Budget")
|
|
239
197
|
}),
|
|
240
198
|
/* @__PURE__ */ c("th", {
|
|
241
199
|
className: "py-1 pr-2 text-right font-normal",
|
|
242
|
-
children:
|
|
200
|
+
children: o("planmagnet.analytics.budget.spent", "Spent")
|
|
243
201
|
}),
|
|
244
202
|
/* @__PURE__ */ c("th", {
|
|
245
203
|
className: "py-1 pr-2 text-right font-normal",
|
|
246
|
-
children:
|
|
204
|
+
children: o("planmagnet.analytics.budget.planned", "Planned")
|
|
247
205
|
}),
|
|
248
206
|
/* @__PURE__ */ c("th", {
|
|
249
207
|
className: "w-2/5 py-1 font-normal",
|
|
250
|
-
children:
|
|
208
|
+
children: o("planmagnet.analytics.budget.usage", "Used")
|
|
251
209
|
})
|
|
252
210
|
]
|
|
253
|
-
}) }), /* @__PURE__ */ c("tbody", { children:
|
|
254
|
-
let
|
|
211
|
+
}) }), /* @__PURE__ */ c("tbody", { children: y.map((i) => {
|
|
212
|
+
let a = n(i.plannedAmount, i.spentAmount), o = t(a), s = e[o], u = a === null ? null : Math.round(a * 100);
|
|
255
213
|
return /* @__PURE__ */ l("tr", {
|
|
256
214
|
className: "border-t align-middle",
|
|
257
215
|
children: [
|
|
@@ -259,48 +217,48 @@ function w({ workspaceId: e }) {
|
|
|
259
217
|
className: "py-2 pr-2",
|
|
260
218
|
children: [/* @__PURE__ */ c("span", {
|
|
261
219
|
className: "font-medium",
|
|
262
|
-
children:
|
|
263
|
-
}),
|
|
220
|
+
children: i.name
|
|
221
|
+
}), i.project ? /* @__PURE__ */ c("span", {
|
|
264
222
|
className: "block text-xs text-muted-foreground",
|
|
265
|
-
children:
|
|
223
|
+
children: i.project.name
|
|
266
224
|
}) : null]
|
|
267
225
|
}),
|
|
268
226
|
/* @__PURE__ */ c("td", {
|
|
269
227
|
className: "py-2 pr-2 text-right tabular-nums",
|
|
270
|
-
children:
|
|
228
|
+
children: r(i.spentAmount, i.currency)
|
|
271
229
|
}),
|
|
272
230
|
/* @__PURE__ */ c("td", {
|
|
273
231
|
className: "py-2 pr-2 text-right tabular-nums",
|
|
274
|
-
children:
|
|
232
|
+
children: r(i.plannedAmount, i.currency)
|
|
275
233
|
}),
|
|
276
234
|
/* @__PURE__ */ l("td", {
|
|
277
235
|
className: "py-2",
|
|
278
236
|
children: [/* @__PURE__ */ c("div", {
|
|
279
237
|
role: "meter",
|
|
280
238
|
"aria-valuemin": 0,
|
|
281
|
-
"aria-valuemax": Math.max(100,
|
|
282
|
-
"aria-valuenow":
|
|
283
|
-
"aria-label": `${
|
|
284
|
-
title: `${
|
|
285
|
-
className: `h-2 w-full overflow-hidden rounded-full ${
|
|
239
|
+
"aria-valuemax": Math.max(100, u ?? 0),
|
|
240
|
+
"aria-valuenow": u ?? 0,
|
|
241
|
+
"aria-label": `${i.name}: ${u === null ? "no plan" : `${u}%`} ${x[o]}`,
|
|
242
|
+
title: `${r(i.spentAmount, i.currency)} of ${r(i.plannedAmount, i.currency)}`,
|
|
243
|
+
className: `h-2 w-full overflow-hidden rounded-full ${s.track}`,
|
|
286
244
|
children: /* @__PURE__ */ c("div", {
|
|
287
|
-
className: `h-full rounded-full ${
|
|
288
|
-
style: { width: `${Math.min(
|
|
245
|
+
className: `h-full rounded-full ${s.fill}`,
|
|
246
|
+
style: { width: `${Math.min(u ?? 0, 100)}%` }
|
|
289
247
|
})
|
|
290
248
|
}), /* @__PURE__ */ l("span", {
|
|
291
|
-
className: `mt-1 flex items-center gap-1 text-xs ${
|
|
292
|
-
children: [/* @__PURE__ */ c(
|
|
249
|
+
className: `mt-1 flex items-center gap-1 text-xs ${s.text}`,
|
|
250
|
+
children: [/* @__PURE__ */ c(s.icon, {
|
|
293
251
|
className: "h-3 w-3",
|
|
294
|
-
"aria-hidden":
|
|
295
|
-
}),
|
|
252
|
+
"aria-hidden": !0
|
|
253
|
+
}), u === null ? x[o] : `${u}% · ${x[o]}`]
|
|
296
254
|
})]
|
|
297
255
|
})
|
|
298
256
|
]
|
|
299
|
-
},
|
|
257
|
+
}, i.id);
|
|
300
258
|
}) })]
|
|
301
259
|
}) })] });
|
|
302
260
|
}
|
|
303
261
|
//#endregion
|
|
304
|
-
export {
|
|
262
|
+
export { b as BudgetPerformanceCard, y as CycleTimeCard };
|
|
305
263
|
|
|
306
264
|
//# sourceMappingURL=DeliveryHealthCards.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeliveryHealthCards.js","names":[],"sources":["../../../../src/planmagnet/pages/analytics/DeliveryHealthCards.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n AlertOctagon,\n AlertTriangle,\n CheckCircle2,\n CircleDashed,\n Timer,\n Wallet,\n} from 'lucide-react';\n\n/**\n * Two analytics the service already computed and no page showed (P1.1).\n *\n * - Cycle time: `planmagnetCycleTime` returned average, median and per-item\n * hours with zero UI consumers.\n * - Budget performance: budgets carry planned / spent / remaining, but the\n * analytics page never surfaced them.\n *\n * Forms follow the dataviz method: headline numbers are stat tiles, per-item\n * detail is a table, and \"spent against a limit\" is a meter whose fill carries\n * severity while its track is a lighter step of the same ramp. Over-budget is a\n * STATUS, so it always ships with an icon and a label — never colour alone.\n * Every colour is a semantic token.\n *\n * Each card owns its query at the top of the component, so neither can land a\n * hook after an early return in the host page.\n */\n\nconst GET_CYCLE_TIME = gql`\n query GetProjectCycleTime($workspaceId: ID!, $projectId: ID!) {\n planmagnetCycleTime(workspaceId: $workspaceId, projectId: $projectId) {\n averageCycleTimeHours\n medianCycleTimeHours\n sampleSize\n items {\n workItemId\n key\n title\n cycleTimeHours\n }\n }\n }\n`;\n\nconst GET_BUDGET_PERFORMANCE = gql`\n query GetBudgetPerformance($workspaceId: ID!) {\n planmagnetBudgets(workspaceId: $workspaceId, pagination: { first: 50 }) {\n items {\n id\n name\n currency\n plannedAmount\n spentAmount\n remainingAmount\n project {\n id\n name\n }\n }\n totalCount\n }\n }\n`;\n\ninterface CycleTimeData {\n planmagnetCycleTime: {\n averageCycleTimeHours: number | null;\n medianCycleTimeHours: number | null;\n sampleSize: number;\n items: Array<{ workItemId: string; key: string; title: string; cycleTimeHours: number }>;\n };\n}\n\ninterface BudgetData {\n planmagnetBudgets: {\n items: Array<{\n id: string;\n name: string;\n currency: string;\n plannedAmount: number;\n spentAmount: number;\n remainingAmount: number;\n project: { id: string; name: string } | null;\n }>;\n totalCount: number;\n };\n}\n\n/** Hours read naturally up to two days; beyond that, days. */\nexport function formatDuration(hours: number | null | undefined): string {\n if (hours === null || hours === undefined || !Number.isFinite(hours)) return '—';\n if (hours < 48) return `${hours < 10 ? hours.toFixed(1) : Math.round(hours)} h`;\n const days = hours / 24;\n return `${days < 10 ? days.toFixed(1) : Math.round(days)} d`;\n}\n\nfunction StatTile({ label, value, hint }: { label: string; value: string; hint?: string }) {\n return (\n <div className=\"rounded-md border p-3\">\n <p className=\"text-xs text-muted-foreground\">{label}</p>\n <p className=\"mt-1 text-2xl font-semibold\">{value}</p>\n {hint ? <p className=\"mt-0.5 text-xs text-muted-foreground\">{hint}</p> : null}\n </div>\n );\n}\n\nexport function CycleTimeCard({\n workspaceId,\n projectId,\n}: {\n workspaceId?: string | null;\n projectId: string;\n}) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<CycleTimeData>(GET_CYCLE_TIME, {\n variables: { workspaceId, projectId },\n skip: !workspaceId || !projectId,\n });\n const cycle = data?.planmagnetCycleTime;\n const slowest = [...(cycle?.items ?? [])]\n .sort((a, b) => b.cycleTimeHours - a.cycleTimeHours)\n .slice(0, 5);\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Timer className=\"h-4 w-4\" />\n {t('planmagnet.analytics.cycleTime.title', 'Cycle time')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.analytics.cycleTime.subtitle',\n 'From first moving to In Progress (or creation) to Done, for the selected project.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {!projectId ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'planmagnet.analytics.cycleTime.noProject',\n 'Select a project to see its cycle time.'\n )}\n </p>\n ) : loading ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.analytics.cycleTime.error', 'Cycle time could not be loaded')}:{' '}\n {error.message}\n </p>\n ) : !cycle || cycle.sampleSize === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'planmagnet.analytics.cycleTime.empty',\n 'No completed work items in this project yet — cycle time appears once items reach Done.'\n )}\n </p>\n ) : (\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-3 gap-3\">\n <StatTile\n label={t('planmagnet.analytics.cycleTime.median', 'Median')}\n value={formatDuration(cycle.medianCycleTimeHours)}\n />\n <StatTile\n label={t('planmagnet.analytics.cycleTime.average', 'Average')}\n value={formatDuration(cycle.averageCycleTimeHours)}\n />\n <StatTile\n label={t('planmagnet.analytics.cycleTime.sample', 'Items measured')}\n value={String(cycle.sampleSize)}\n />\n </div>\n <table className=\"w-full text-sm\">\n <caption className=\"mb-2 text-left text-xs text-muted-foreground\">\n {t('planmagnet.analytics.cycleTime.slowest', 'Slowest completed items')}\n </caption>\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-1 pr-2 font-normal\">\n {t('planmagnet.analytics.cycleTime.item', 'Item')}\n </th>\n <th className=\"py-1 text-right font-normal\">\n {t('planmagnet.analytics.cycleTime.duration', 'Cycle time')}\n </th>\n </tr>\n </thead>\n <tbody>\n {slowest.map((item) => (\n <tr key={item.workItemId} className=\"border-t\">\n <td className=\"py-1.5 pr-2\">\n <span className=\"font-medium\">{item.key}</span>{' '}\n <span className=\"text-muted-foreground\">{item.title}</span>\n </td>\n <td className=\"py-1.5 text-right tabular-nums\">\n {formatDuration(item.cycleTimeHours)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n\ntype BudgetState = 'no-plan' | 'on-track' | 'near-limit' | 'over-budget';\n\n/** Share of the plan spent; `null` when there is no plan to measure against. */\nexport function budgetUsage(planned: number, spent: number): number | null {\n if (!Number.isFinite(planned) || planned <= 0) return null;\n return spent / planned;\n}\n\nexport function budgetState(usage: number | null): BudgetState {\n // Without a plan there is nothing to be on track AGAINST — say so, rather\n // than reporting a comforting \"On track\" the numbers cannot support.\n if (usage === null) return 'no-plan';\n if (usage <= 0.85) return 'on-track';\n return usage <= 1 ? 'near-limit' : 'over-budget';\n}\n\nconst STATE_STYLE: Record<\n BudgetState,\n { track: string; fill: string; Icon: typeof CheckCircle2; text: string }\n> = {\n 'no-plan': {\n track: 'bg-muted',\n fill: 'bg-muted',\n Icon: CircleDashed,\n text: 'text-muted-foreground',\n },\n 'on-track': {\n track: 'bg-primary/15',\n fill: 'bg-primary',\n Icon: CheckCircle2,\n text: 'text-muted-foreground',\n },\n 'near-limit': {\n track: 'bg-status-warning-subtle',\n fill: 'bg-status-warning',\n Icon: AlertTriangle,\n text: 'text-status-warning-text',\n },\n 'over-budget': {\n track: 'bg-status-error-subtle',\n fill: 'bg-status-error',\n Icon: AlertOctagon,\n text: 'text-status-error-text',\n },\n};\n\nfunction money(amount: number, currency: string): string {\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n maximumFractionDigits: 0,\n }).format(amount);\n } catch {\n // An unrecognised currency code must not blank the whole table.\n return `${Math.round(amount).toLocaleString()} ${currency}`;\n }\n}\n\nexport function BudgetPerformanceCard({ workspaceId }: { workspaceId?: string | null }) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<BudgetData>(GET_BUDGET_PERFORMANCE, {\n variables: { workspaceId },\n skip: !workspaceId,\n });\n // Riskiest first: over budget, then by share spent (BOFF-7996). The page holds the newest 50; when there are more,\n // the subtitle says so instead of claiming the whole workspace.\n const budgets = [...(data?.planmagnetBudgets?.items ?? [])].sort(\n (a, b) =>\n (budgetUsage(b.plannedAmount, b.spentAmount) ?? -1) -\n (budgetUsage(a.plannedAmount, a.spentAmount) ?? -1)\n );\n const totalBudgets = data?.planmagnetBudgets?.totalCount ?? budgets.length;\n const label: Record<BudgetState, string> = {\n 'no-plan': t('planmagnet.analytics.budget.noPlan', 'No plan set'),\n 'on-track': t('planmagnet.analytics.budget.onTrack', 'On track'),\n 'near-limit': t('planmagnet.analytics.budget.nearLimit', 'Near limit'),\n 'over-budget': t('planmagnet.analytics.budget.over', 'Over budget'),\n };\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Wallet className=\"h-4 w-4\" />\n {t('planmagnet.analytics.budget.title', 'Budget performance')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {totalBudgets > budgets.length\n ? t(\n 'planmagnet.analytics.budget.subtitlePartial',\n 'Spent against plan, per budget — the newest {{shown}} of {{total}}, riskiest first.',\n { shown: budgets.length, total: totalBudgets }\n )\n : t(\n 'planmagnet.analytics.budget.subtitle',\n 'Spent against plan, per budget, across the workspace.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {loading ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.analytics.budget.error', 'Budgets could not be loaded')}: {error.message}\n </p>\n ) : budgets.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.budget.empty', 'No budgets in this workspace yet.')}\n </p>\n ) : (\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-1 pr-2 font-normal\">\n {t('planmagnet.analytics.budget.name', 'Budget')}\n </th>\n <th className=\"py-1 pr-2 text-right font-normal\">\n {t('planmagnet.analytics.budget.spent', 'Spent')}\n </th>\n <th className=\"py-1 pr-2 text-right font-normal\">\n {t('planmagnet.analytics.budget.planned', 'Planned')}\n </th>\n <th className=\"w-2/5 py-1 font-normal\">\n {t('planmagnet.analytics.budget.usage', 'Used')}\n </th>\n </tr>\n </thead>\n <tbody>\n {budgets.map((budget) => {\n const usage = budgetUsage(budget.plannedAmount, budget.spentAmount);\n const state = budgetState(usage);\n const style = STATE_STYLE[state];\n const percent = usage === null ? null : Math.round(usage * 100);\n return (\n <tr key={budget.id} className=\"border-t align-middle\">\n <td className=\"py-2 pr-2\">\n <span className=\"font-medium\">{budget.name}</span>\n {budget.project ? (\n <span className=\"block text-xs text-muted-foreground\">\n {budget.project.name}\n </span>\n ) : null}\n </td>\n <td className=\"py-2 pr-2 text-right tabular-nums\">\n {money(budget.spentAmount, budget.currency)}\n </td>\n <td className=\"py-2 pr-2 text-right tabular-nums\">\n {money(budget.plannedAmount, budget.currency)}\n </td>\n <td className=\"py-2\">\n <div\n role=\"meter\"\n aria-valuemin={0}\n // ARIA requires valuenow to sit inside [min, max]. An\n // over-budget 130% with a max of 100 is invalid, and\n // assistive tech may clamp it and announce \"100%\".\n aria-valuemax={Math.max(100, percent ?? 0)}\n aria-valuenow={percent ?? 0}\n aria-label={`${budget.name}: ${percent === null ? 'no plan' : `${percent}%`} ${label[state]}`}\n title={`${money(budget.spentAmount, budget.currency)} of ${money(budget.plannedAmount, budget.currency)}`}\n className={`h-2 w-full overflow-hidden rounded-full ${style.track}`}\n >\n <div\n className={`h-full rounded-full ${style.fill}`}\n style={{ width: `${Math.min(percent ?? 0, 100)}%` }}\n />\n </div>\n <span className={`mt-1 flex items-center gap-1 text-xs ${style.text}`}>\n <style.Icon className=\"h-3 w-3\" aria-hidden=\"true\" />\n {percent === null ? label[state] : `${percent}% · ${label[state]}`}\n </span>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DeliveryHealthCards.js","names":[],"sources":["../../../../src/planmagnet/pages/analytics/DeliveryHealthCards.tsx"],"sourcesContent":["import { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Timer, Wallet } from 'lucide-react';\nimport {\n BUDGET_STATE_STYLE,\n budgetState,\n budgetUsage,\n money,\n type BudgetState,\n} from '../../budgets/budgetMath';\n\n/**\n * Two analytics the service already computed and no page showed (P1.1).\n *\n * - Cycle time: `planmagnetCycleTime` returned average, median and per-item\n * hours with zero UI consumers.\n * - Budget performance: budgets carry planned / spent / remaining, but the\n * analytics page never surfaced them.\n *\n * Forms follow the dataviz method: headline numbers are stat tiles, per-item\n * detail is a table, and \"spent against a limit\" is a meter whose fill carries\n * severity while its track is a lighter step of the same ramp. Over-budget is a\n * STATUS, so it always ships with an icon and a label — never colour alone.\n * Every colour is a semantic token.\n *\n * Each card owns its query at the top of the component, so neither can land a\n * hook after an early return in the host page.\n */\n\nconst GET_CYCLE_TIME = gql`\n query GetProjectCycleTime($workspaceId: ID!, $projectId: ID!) {\n planmagnetCycleTime(workspaceId: $workspaceId, projectId: $projectId) {\n averageCycleTimeHours\n medianCycleTimeHours\n sampleSize\n items {\n workItemId\n key\n title\n cycleTimeHours\n }\n }\n }\n`;\n\nconst GET_BUDGET_PERFORMANCE = gql`\n query GetBudgetPerformance($workspaceId: ID!) {\n planmagnetBudgets(workspaceId: $workspaceId, pagination: { first: 50 }) {\n items {\n id\n name\n currency\n plannedAmount\n spentAmount\n remainingAmount\n project {\n id\n name\n }\n }\n totalCount\n }\n }\n`;\n\ninterface CycleTimeData {\n planmagnetCycleTime: {\n averageCycleTimeHours: number | null;\n medianCycleTimeHours: number | null;\n sampleSize: number;\n items: Array<{ workItemId: string; key: string; title: string; cycleTimeHours: number }>;\n };\n}\n\ninterface BudgetData {\n planmagnetBudgets: {\n items: Array<{\n id: string;\n name: string;\n currency: string;\n plannedAmount: number;\n spentAmount: number;\n remainingAmount: number;\n project: { id: string; name: string } | null;\n }>;\n totalCount: number;\n };\n}\n\n/** Hours read naturally up to two days; beyond that, days. */\nexport function formatDuration(hours: number | null | undefined): string {\n if (hours === null || hours === undefined || !Number.isFinite(hours)) return '—';\n if (hours < 48) return `${hours < 10 ? hours.toFixed(1) : Math.round(hours)} h`;\n const days = hours / 24;\n return `${days < 10 ? days.toFixed(1) : Math.round(days)} d`;\n}\n\nfunction StatTile({ label, value, hint }: { label: string; value: string; hint?: string }) {\n return (\n <div className=\"rounded-md border p-3\">\n <p className=\"text-xs text-muted-foreground\">{label}</p>\n <p className=\"mt-1 text-2xl font-semibold\">{value}</p>\n {hint ? <p className=\"mt-0.5 text-xs text-muted-foreground\">{hint}</p> : null}\n </div>\n );\n}\n\nexport function CycleTimeCard({\n workspaceId,\n projectId,\n}: {\n workspaceId?: string | null;\n projectId: string;\n}) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<CycleTimeData>(GET_CYCLE_TIME, {\n variables: { workspaceId, projectId },\n skip: !workspaceId || !projectId,\n });\n const cycle = data?.planmagnetCycleTime;\n const slowest = [...(cycle?.items ?? [])]\n .sort((a, b) => b.cycleTimeHours - a.cycleTimeHours)\n .slice(0, 5);\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Timer className=\"h-4 w-4\" />\n {t('planmagnet.analytics.cycleTime.title', 'Cycle time')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'planmagnet.analytics.cycleTime.subtitle',\n 'From first moving to In Progress (or creation) to Done, for the selected project.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {!projectId ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'planmagnet.analytics.cycleTime.noProject',\n 'Select a project to see its cycle time.'\n )}\n </p>\n ) : loading ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.analytics.cycleTime.error', 'Cycle time could not be loaded')}:{' '}\n {error.message}\n </p>\n ) : !cycle || cycle.sampleSize === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'planmagnet.analytics.cycleTime.empty',\n 'No completed work items in this project yet — cycle time appears once items reach Done.'\n )}\n </p>\n ) : (\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-3 gap-3\">\n <StatTile\n label={t('planmagnet.analytics.cycleTime.median', 'Median')}\n value={formatDuration(cycle.medianCycleTimeHours)}\n />\n <StatTile\n label={t('planmagnet.analytics.cycleTime.average', 'Average')}\n value={formatDuration(cycle.averageCycleTimeHours)}\n />\n <StatTile\n label={t('planmagnet.analytics.cycleTime.sample', 'Items measured')}\n value={String(cycle.sampleSize)}\n />\n </div>\n <table className=\"w-full text-sm\">\n <caption className=\"mb-2 text-left text-xs text-muted-foreground\">\n {t('planmagnet.analytics.cycleTime.slowest', 'Slowest completed items')}\n </caption>\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-1 pr-2 font-normal\">\n {t('planmagnet.analytics.cycleTime.item', 'Item')}\n </th>\n <th className=\"py-1 text-right font-normal\">\n {t('planmagnet.analytics.cycleTime.duration', 'Cycle time')}\n </th>\n </tr>\n </thead>\n <tbody>\n {slowest.map((item) => (\n <tr key={item.workItemId} className=\"border-t\">\n <td className=\"py-1.5 pr-2\">\n <span className=\"font-medium\">{item.key}</span>{' '}\n <span className=\"text-muted-foreground\">{item.title}</span>\n </td>\n <td className=\"py-1.5 text-right tabular-nums\">\n {formatDuration(item.cycleTimeHours)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n\nexport function BudgetPerformanceCard({ workspaceId }: { workspaceId?: string | null }) {\n const { t } = useI18n();\n const { data, loading, error } = useQuery<BudgetData>(GET_BUDGET_PERFORMANCE, {\n variables: { workspaceId },\n skip: !workspaceId,\n });\n // Riskiest first: over budget, then by share spent (BOFF-7996). The page holds the newest 50; when there are more,\n // the subtitle says so instead of claiming the whole workspace.\n const budgets = [...(data?.planmagnetBudgets?.items ?? [])].sort(\n (a, b) =>\n (budgetUsage(b.plannedAmount, b.spentAmount) ?? -1) -\n (budgetUsage(a.plannedAmount, a.spentAmount) ?? -1)\n );\n const totalBudgets = data?.planmagnetBudgets?.totalCount ?? budgets.length;\n const label: Record<BudgetState, string> = {\n 'no-plan': t('planmagnet.analytics.budget.noPlan', 'No plan set'),\n 'on-track': t('planmagnet.analytics.budget.onTrack', 'On track'),\n 'near-limit': t('planmagnet.analytics.budget.nearLimit', 'Near limit'),\n 'over-budget': t('planmagnet.analytics.budget.over', 'Over budget'),\n };\n\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Wallet className=\"h-4 w-4\" />\n {t('planmagnet.analytics.budget.title', 'Budget performance')}\n </CardTitle>\n <p className=\"text-xs text-muted-foreground\">\n {totalBudgets > budgets.length\n ? t(\n 'planmagnet.analytics.budget.subtitlePartial',\n 'Spent against plan, per budget — the newest {{shown}} of {{total}}, riskiest first.',\n { shown: budgets.length, total: totalBudgets }\n )\n : t(\n 'planmagnet.analytics.budget.subtitle',\n 'Spent against plan, per budget, across the workspace.'\n )}\n </p>\n </CardHeader>\n <CardContent>\n {loading ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.loading', 'Loading…')}\n </p>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">\n {t('planmagnet.analytics.budget.error', 'Budgets could not be loaded')}: {error.message}\n </p>\n ) : budgets.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('planmagnet.analytics.budget.empty', 'No budgets in this workspace yet.')}\n </p>\n ) : (\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"text-left text-xs text-muted-foreground\">\n <th className=\"py-1 pr-2 font-normal\">\n {t('planmagnet.analytics.budget.name', 'Budget')}\n </th>\n <th className=\"py-1 pr-2 text-right font-normal\">\n {t('planmagnet.analytics.budget.spent', 'Spent')}\n </th>\n <th className=\"py-1 pr-2 text-right font-normal\">\n {t('planmagnet.analytics.budget.planned', 'Planned')}\n </th>\n <th className=\"w-2/5 py-1 font-normal\">\n {t('planmagnet.analytics.budget.usage', 'Used')}\n </th>\n </tr>\n </thead>\n <tbody>\n {budgets.map((budget) => {\n const usage = budgetUsage(budget.plannedAmount, budget.spentAmount);\n const state = budgetState(usage);\n const style = BUDGET_STATE_STYLE[state];\n const percent = usage === null ? null : Math.round(usage * 100);\n return (\n <tr key={budget.id} className=\"border-t align-middle\">\n <td className=\"py-2 pr-2\">\n <span className=\"font-medium\">{budget.name}</span>\n {budget.project ? (\n <span className=\"block text-xs text-muted-foreground\">\n {budget.project.name}\n </span>\n ) : null}\n </td>\n <td className=\"py-2 pr-2 text-right tabular-nums\">\n {money(budget.spentAmount, budget.currency)}\n </td>\n <td className=\"py-2 pr-2 text-right tabular-nums\">\n {money(budget.plannedAmount, budget.currency)}\n </td>\n <td className=\"py-2\">\n <div\n role=\"meter\"\n aria-valuemin={0}\n // ARIA requires valuenow to sit inside [min, max]. An\n // over-budget 130% with a max of 100 is invalid, and\n // assistive tech may clamp it and announce \"100%\".\n aria-valuemax={Math.max(100, percent ?? 0)}\n aria-valuenow={percent ?? 0}\n aria-label={`${budget.name}: ${percent === null ? 'no plan' : `${percent}%`} ${label[state]}`}\n title={`${money(budget.spentAmount, budget.currency)} of ${money(budget.plannedAmount, budget.currency)}`}\n className={`h-2 w-full overflow-hidden rounded-full ${style.track}`}\n >\n <div\n className={`h-full rounded-full ${style.fill}`}\n style={{ width: `${Math.min(percent ?? 0, 100)}%` }}\n />\n </div>\n <span className={`mt-1 flex items-center gap-1 text-xs ${style.text}`}>\n <style.icon className=\"h-3 w-3\" aria-hidden={true} />\n {percent === null ? label[state] : `${percent}% · ${label[state]}`}\n </span>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
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 {
|
|
3
|
+
import { ConfirmButton as r, ErrorLine as i } from "../controls/ConfirmButton.js";
|
|
4
|
+
import { enumLabel as a } from "../controls/WorkItemControls.js";
|
|
5
|
+
import { detailPath as o } from "../utils/rowNavigation.js";
|
|
6
|
+
import { endBeforeStart as s } from "../utils/formValidation.js";
|
|
7
7
|
import { useState as c } from "react";
|
|
8
8
|
import { BarChart3 as l, Pencil as u, Plus as d, Trash2 as f } from "lucide-react";
|
|
9
9
|
import { gql as p } from "@apollo/client";
|
|
@@ -52,17 +52,17 @@ var E = [
|
|
|
52
52
|
}
|
|
53
53
|
`, M = (e) => e ? e.slice(0, 10) : "";
|
|
54
54
|
function N({ roadmapId: e, item: t, lanes: r }) {
|
|
55
|
-
let
|
|
55
|
+
let o = {
|
|
56
56
|
title: t?.title ?? "",
|
|
57
57
|
description: t?.description ?? "",
|
|
58
58
|
status: t?.status ?? "PLANNED",
|
|
59
59
|
swimlane: t?.swimlane ?? "",
|
|
60
60
|
startDate: M(t?.startDate),
|
|
61
61
|
endDate: M(t?.endDate)
|
|
62
|
-
}, [l, f] = c(!1), [p, A] = c(
|
|
62
|
+
}, [l, f] = c(!1), [p, A] = c(o), [j, N] = c(null), P = {
|
|
63
63
|
refetchQueries: D,
|
|
64
64
|
awaitRefetchQueries: !0
|
|
65
|
-
}, [F, I] = m(O, P), [L, R] = m(k, P), z = I.loading || R.loading, B =
|
|
65
|
+
}, [F, I] = m(O, P), [L, R] = m(k, P), z = I.loading || R.loading, B = s(p.startDate, p.endDate), V = p.title.trim() !== "" && !B && !z, H = E.includes(p.status) ? E : [p.status, ...E], U = t ? `roadmap-item-${t.id}` : "roadmap-item-new", W = async () => {
|
|
66
66
|
N(null);
|
|
67
67
|
let r = {
|
|
68
68
|
title: p.title.trim(),
|
|
@@ -73,8 +73,8 @@ function N({ roadmapId: e, item: t, lanes: r }) {
|
|
|
73
73
|
try {
|
|
74
74
|
if (t) {
|
|
75
75
|
let e = {
|
|
76
|
-
...p.startDate !==
|
|
77
|
-
...p.endDate !==
|
|
76
|
+
...p.startDate !== o.startDate && { startDate: p.startDate || null },
|
|
77
|
+
...p.endDate !== o.endDate && { endDate: p.endDate || null }
|
|
78
78
|
};
|
|
79
79
|
await n(L({ variables: {
|
|
80
80
|
id: t.id,
|
|
@@ -88,7 +88,7 @@ function N({ roadmapId: e, item: t, lanes: r }) {
|
|
|
88
88
|
...r,
|
|
89
89
|
startDate: p.startDate || void 0,
|
|
90
90
|
endDate: p.endDate || void 0
|
|
91
|
-
} } })), A(
|
|
91
|
+
} } })), A(o);
|
|
92
92
|
f(!1);
|
|
93
93
|
} catch (e) {
|
|
94
94
|
N(e instanceof Error ? e.message : "The item could not be saved.");
|
|
@@ -97,7 +97,7 @@ function N({ roadmapId: e, item: t, lanes: r }) {
|
|
|
97
97
|
return /* @__PURE__ */ g(y, {
|
|
98
98
|
open: l,
|
|
99
99
|
onOpenChange: (e) => {
|
|
100
|
-
f(e), e ? A(
|
|
100
|
+
f(e), e ? A(o) : N(null);
|
|
101
101
|
},
|
|
102
102
|
children: [/* @__PURE__ */ h(T, {
|
|
103
103
|
asChild: !0,
|
|
@@ -168,7 +168,7 @@ function N({ roadmapId: e, item: t, lanes: r }) {
|
|
|
168
168
|
}),
|
|
169
169
|
children: H.map((e) => /* @__PURE__ */ h("option", {
|
|
170
170
|
value: e,
|
|
171
|
-
children:
|
|
171
|
+
children: a(e)
|
|
172
172
|
}, e))
|
|
173
173
|
})] })]
|
|
174
174
|
}),
|
|
@@ -204,7 +204,7 @@ function N({ roadmapId: e, item: t, lanes: r }) {
|
|
|
204
204
|
})
|
|
205
205
|
})] })]
|
|
206
206
|
}),
|
|
207
|
-
B && /* @__PURE__ */ h(
|
|
207
|
+
B && /* @__PURE__ */ h(i, { message: "The end date is before the start date." }),
|
|
208
208
|
/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
209
209
|
htmlFor: `${U}-description`,
|
|
210
210
|
className: "text-sm font-medium",
|
|
@@ -218,7 +218,7 @@ function N({ roadmapId: e, item: t, lanes: r }) {
|
|
|
218
218
|
description: e.target.value
|
|
219
219
|
})
|
|
220
220
|
})] }),
|
|
221
|
-
j && /* @__PURE__ */ h(
|
|
221
|
+
j && /* @__PURE__ */ h(i, { message: j })
|
|
222
222
|
]
|
|
223
223
|
}),
|
|
224
224
|
/* @__PURE__ */ g(S, { children: [/* @__PURE__ */ h(_, {
|
|
@@ -235,20 +235,20 @@ function N({ roadmapId: e, item: t, lanes: r }) {
|
|
|
235
235
|
});
|
|
236
236
|
}
|
|
237
237
|
function P({ id: e, title: t }) {
|
|
238
|
-
let [
|
|
238
|
+
let [a, o] = m(A, {
|
|
239
239
|
refetchQueries: D,
|
|
240
240
|
awaitRefetchQueries: !0
|
|
241
241
|
}), [s, l] = c(null), u = async () => {
|
|
242
242
|
l(null);
|
|
243
243
|
try {
|
|
244
|
-
await n(
|
|
244
|
+
await n(a({ variables: { id: e } }));
|
|
245
245
|
} catch (e) {
|
|
246
246
|
l(e instanceof Error ? e.message : "The item could not be removed.");
|
|
247
247
|
}
|
|
248
248
|
};
|
|
249
249
|
return /* @__PURE__ */ g("div", {
|
|
250
250
|
className: "flex flex-col items-end",
|
|
251
|
-
children: [/* @__PURE__ */ h(
|
|
251
|
+
children: [/* @__PURE__ */ h(r, {
|
|
252
252
|
label: "",
|
|
253
253
|
ariaLabel: `Remove ${t}`,
|
|
254
254
|
variant: "outline",
|
|
@@ -259,17 +259,17 @@ function P({ id: e, title: t }) {
|
|
|
259
259
|
destructive: !0,
|
|
260
260
|
disabled: o.loading,
|
|
261
261
|
onConfirm: () => void u()
|
|
262
|
-
}), s && /* @__PURE__ */ h(
|
|
262
|
+
}), s && /* @__PURE__ */ h(i, { message: s })]
|
|
263
263
|
});
|
|
264
264
|
}
|
|
265
|
-
function F({ roadmapId:
|
|
265
|
+
function F({ roadmapId: a, name: s }) {
|
|
266
266
|
let d = e(), [p, v] = m(j, {
|
|
267
267
|
refetchQueries: ["ListRoadmapsPage", "ListRoadmaps"],
|
|
268
268
|
context: t
|
|
269
269
|
}), [y, b] = c(null), x = async () => {
|
|
270
270
|
b(null);
|
|
271
271
|
try {
|
|
272
|
-
await n(p({ variables: { id:
|
|
272
|
+
await n(p({ variables: { id: a } })), d("/products/roadmaps-page");
|
|
273
273
|
} catch (e) {
|
|
274
274
|
b(e instanceof Error ? e.message : "The roadmap could not be deleted.");
|
|
275
275
|
}
|
|
@@ -283,17 +283,17 @@ function F({ roadmapId: o, name: s }) {
|
|
|
283
283
|
size: "sm",
|
|
284
284
|
variant: "outline",
|
|
285
285
|
"data-testid": "roadmap-edit",
|
|
286
|
-
onClick: () => d(
|
|
286
|
+
onClick: () => d(o("/products/edit-product-page", a)),
|
|
287
287
|
children: [/* @__PURE__ */ h(u, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
288
288
|
}),
|
|
289
289
|
/* @__PURE__ */ g(_, {
|
|
290
290
|
size: "sm",
|
|
291
291
|
variant: "outline",
|
|
292
292
|
"data-testid": "roadmap-analytics",
|
|
293
|
-
onClick: () => d(
|
|
293
|
+
onClick: () => d(o("/products/roadmap-analytics-page", a)),
|
|
294
294
|
children: [/* @__PURE__ */ h(l, { className: "mr-1 h-4 w-4" }), "Analytics"]
|
|
295
295
|
}),
|
|
296
|
-
/* @__PURE__ */ h(
|
|
296
|
+
/* @__PURE__ */ h(r, {
|
|
297
297
|
label: "Delete",
|
|
298
298
|
variant: "outline",
|
|
299
299
|
icon: /* @__PURE__ */ h(f, { className: "mr-1 h-4 w-4" }),
|
|
@@ -305,7 +305,7 @@ function F({ roadmapId: o, name: s }) {
|
|
|
305
305
|
onConfirm: () => void x()
|
|
306
306
|
})
|
|
307
307
|
]
|
|
308
|
-
}), y && /* @__PURE__ */ h(
|
|
308
|
+
}), y && /* @__PURE__ */ h(i, { message: y })]
|
|
309
309
|
});
|
|
310
310
|
}
|
|
311
311
|
//#endregion
|
|
@@ -27,13 +27,25 @@ export interface SprintWorkItem {
|
|
|
27
27
|
status: string;
|
|
28
28
|
storyPoints: number | null;
|
|
29
29
|
}
|
|
30
|
-
export declare function SprintWorkItemsPanel({ sprintId,
|
|
30
|
+
export declare function SprintWorkItemsPanel({ sprintId, eligibleProjectIds, projectNames, status, items, }: {
|
|
31
31
|
sprintId: string;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The project(s) this sprint may draw work items from (BOFF-8044). A project-scoped sprint
|
|
34
|
+
* always has exactly one; a team-scoped sprint has the team's own live member projects (which
|
|
35
|
+
* can be zero if the team currently has none, or more than one for a real cross-project team) —
|
|
36
|
+
* never the whole workspace. See AddWorkItemsDialog for how a length > 1 is queried.
|
|
37
|
+
*/
|
|
38
|
+
eligibleProjectIds: string[];
|
|
39
|
+
/** Project id -> name, for labelling candidates when more than one project is eligible. */
|
|
40
|
+
projectNames?: Record<string, string>;
|
|
33
41
|
status: string;
|
|
34
42
|
items: SprintWorkItem[];
|
|
35
43
|
}): import("react/jsx-runtime").JSX.Element;
|
|
36
|
-
export
|
|
44
|
+
export type SprintScope = 'project' | 'team';
|
|
45
|
+
export declare function NewSprintDialog({ onCreated, initialScope, initialTeamId, }: {
|
|
37
46
|
onCreated: (sprintId: string) => void;
|
|
47
|
+
/** BOFF-8044: preset for a caller that already knows the scope, e.g. the Team detail page. */
|
|
48
|
+
initialScope?: SprintScope;
|
|
49
|
+
initialTeamId?: string;
|
|
38
50
|
}): import("react/jsx-runtime").JSX.Element;
|
|
39
51
|
//# sourceMappingURL=SprintControls.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SprintControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/sprints/SprintControls.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SprintControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/sprints/SprintControls.tsx"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AACxF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAQ3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAI/D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD;AA8BD,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,UAAU,EACV,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,kDA2DA;AAmCD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AASD,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,kBAAkB,EAClB,YAAY,EACZ,MAAM,EACN,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB,2CAuFA;AA0PD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;AAE7C,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,YAAY,EACZ,aAAa,GACd,EAAE;IACD,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,8FAA8F;IAC9F,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,2CAqPA"}
|