@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.
Files changed (226) hide show
  1. package/dist/planmagnet/PlanMagnetRoot.d.ts.map +1 -1
  2. package/dist/planmagnet/PlanMagnetRoot.js +130 -97
  3. package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
  4. package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
  5. package/dist/planmagnet/PlanMagnetRoutes.js +256 -156
  6. package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
  7. package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts +5 -0
  8. package/dist/planmagnet/automation/AutomationExecutionsPanel.d.ts.map +1 -0
  9. package/dist/planmagnet/automation/AutomationExecutionsPanel.js +107 -0
  10. package/dist/planmagnet/automation/AutomationExecutionsPanel.js.map +1 -0
  11. package/dist/planmagnet/automation/AutomationRuleFields.d.ts +29 -0
  12. package/dist/planmagnet/automation/AutomationRuleFields.d.ts.map +1 -0
  13. package/dist/planmagnet/automation/AutomationRuleFields.js +308 -0
  14. package/dist/planmagnet/automation/AutomationRuleFields.js.map +1 -0
  15. package/dist/planmagnet/automation/automationRuleTypes.d.ts +60 -0
  16. package/dist/planmagnet/automation/automationRuleTypes.d.ts.map +1 -0
  17. package/dist/planmagnet/automation/automationRuleTypes.js +113 -0
  18. package/dist/planmagnet/automation/automationRuleTypes.js.map +1 -0
  19. package/dist/planmagnet/board/BulkActions.d.ts +11 -0
  20. package/dist/planmagnet/board/BulkActions.d.ts.map +1 -0
  21. package/dist/planmagnet/board/BulkActions.js +182 -0
  22. package/dist/planmagnet/board/BulkActions.js.map +1 -0
  23. package/dist/planmagnet/board/ProjectBoard.d.ts.map +1 -1
  24. package/dist/planmagnet/board/ProjectBoard.js +289 -254
  25. package/dist/planmagnet/board/ProjectBoard.js.map +1 -1
  26. package/dist/planmagnet/budgets/BudgetControls.d.ts +30 -0
  27. package/dist/planmagnet/budgets/BudgetControls.d.ts.map +1 -0
  28. package/dist/planmagnet/budgets/BudgetControls.js +470 -0
  29. package/dist/planmagnet/budgets/BudgetControls.js.map +1 -0
  30. package/dist/planmagnet/budgets/ExpenseControls.d.ts +27 -0
  31. package/dist/planmagnet/budgets/ExpenseControls.d.ts.map +1 -0
  32. package/dist/planmagnet/budgets/ExpenseControls.js +366 -0
  33. package/dist/planmagnet/budgets/ExpenseControls.js.map +1 -0
  34. package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts +5 -0
  35. package/dist/planmagnet/budgets/ProjectBudgetsSummary.d.ts.map +1 -0
  36. package/dist/planmagnet/budgets/ProjectBudgetsSummary.js +112 -0
  37. package/dist/planmagnet/budgets/ProjectBudgetsSummary.js.map +1 -0
  38. package/dist/planmagnet/budgets/budgetMath.d.ts +26 -0
  39. package/dist/planmagnet/budgets/budgetMath.d.ts.map +1 -0
  40. package/dist/planmagnet/budgets/budgetMath.js +49 -0
  41. package/dist/planmagnet/budgets/budgetMath.js.map +1 -0
  42. package/dist/planmagnet/controls/AutomationRuleControls.d.ts +23 -0
  43. package/dist/planmagnet/controls/AutomationRuleControls.d.ts.map +1 -0
  44. package/dist/planmagnet/controls/AutomationRuleControls.js +172 -0
  45. package/dist/planmagnet/controls/AutomationRuleControls.js.map +1 -0
  46. package/dist/planmagnet/controls/CompetitorControls.js +15 -15
  47. package/dist/planmagnet/controls/CustomFieldControls.d.ts +42 -0
  48. package/dist/planmagnet/controls/CustomFieldControls.d.ts.map +1 -0
  49. package/dist/planmagnet/controls/CustomFieldControls.js +84 -0
  50. package/dist/planmagnet/controls/CustomFieldControls.js.map +1 -0
  51. package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts +8 -0
  52. package/dist/planmagnet/controls/CustomFieldValuesPanel.d.ts.map +1 -0
  53. package/dist/planmagnet/controls/CustomFieldValuesPanel.js +315 -0
  54. package/dist/planmagnet/controls/CustomFieldValuesPanel.js.map +1 -0
  55. package/dist/planmagnet/controls/FeatureControls.js +54 -54
  56. package/dist/planmagnet/controls/ObjectiveControls.js +15 -15
  57. package/dist/planmagnet/controls/Pickers.d.ts +7 -0
  58. package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
  59. package/dist/planmagnet/controls/Pickers.js +58 -20
  60. package/dist/planmagnet/controls/Pickers.js.map +1 -1
  61. package/dist/planmagnet/controls/PortfolioControls.js +7 -7
  62. package/dist/planmagnet/controls/ProgramControls.js +19 -19
  63. package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -1
  64. package/dist/planmagnet/controls/ProjectControls.js +54 -47
  65. package/dist/planmagnet/controls/ProjectControls.js.map +1 -1
  66. package/dist/planmagnet/controls/ReleaseControls.js +7 -7
  67. package/dist/planmagnet/controls/RiskControls.d.ts +27 -0
  68. package/dist/planmagnet/controls/RiskControls.d.ts.map +1 -0
  69. package/dist/planmagnet/controls/RiskControls.js +346 -0
  70. package/dist/planmagnet/controls/RiskControls.js.map +1 -0
  71. package/dist/planmagnet/controls/TemplateControls.d.ts +15 -0
  72. package/dist/planmagnet/controls/TemplateControls.d.ts.map +1 -0
  73. package/dist/planmagnet/controls/TemplateControls.js +55 -0
  74. package/dist/planmagnet/controls/TemplateControls.js.map +1 -0
  75. package/dist/planmagnet/controls/WorkItemControls.d.ts +6 -2
  76. package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -1
  77. package/dist/planmagnet/controls/WorkItemControls.js +9 -9
  78. package/dist/planmagnet/controls/WorkItemControls.js.map +1 -1
  79. package/dist/planmagnet/create/CreateDialogs.d.ts +20 -0
  80. package/dist/planmagnet/create/CreateDialogs.d.ts.map +1 -1
  81. package/dist/planmagnet/create/CreateDialogs.js +455 -116
  82. package/dist/planmagnet/create/CreateDialogs.js.map +1 -1
  83. package/dist/planmagnet/dependency/DependencyPanel.d.ts +6 -0
  84. package/dist/planmagnet/dependency/DependencyPanel.d.ts.map +1 -0
  85. package/dist/planmagnet/dependency/DependencyPanel.js +310 -0
  86. package/dist/planmagnet/dependency/DependencyPanel.js.map +1 -0
  87. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts +2 -0
  88. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.d.ts.map +1 -0
  89. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js +171 -0
  90. package/dist/planmagnet/pages/AutomationAutomationRuleDetailPagePage.js.map +1 -0
  91. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts +2 -0
  92. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.d.ts.map +1 -0
  93. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js +232 -0
  94. package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js.map +1 -0
  95. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts +2 -0
  96. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.d.ts.map +1 -0
  97. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +93 -0
  98. package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js.map +1 -0
  99. package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  100. package/dist/planmagnet/pages/DashboardDashboardPagePage.js +11 -3
  101. package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
  102. package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js +5 -5
  103. package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +9 -9
  104. package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js +6 -6
  105. package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js +4 -4
  106. package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js +6 -6
  107. package/dist/planmagnet/pages/ProgramsProgramsPagePage.js +5 -5
  108. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts +2 -0
  109. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.d.ts.map +1 -0
  110. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js +315 -0
  111. package/dist/planmagnet/pages/ProjectsBudgetDetailPagePage.js.map +1 -0
  112. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts +2 -0
  113. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.d.ts.map +1 -0
  114. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +186 -0
  115. package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js.map +1 -0
  116. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts +2 -0
  117. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.d.ts.map +1 -0
  118. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +164 -0
  119. package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js.map +1 -0
  120. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts +2 -0
  121. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.d.ts.map +1 -0
  122. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js +348 -0
  123. package/dist/planmagnet/pages/ProjectsCustomFieldsPagePage.js.map +1 -0
  124. package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +10 -10
  125. package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +4 -4
  126. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
  127. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +20 -10
  128. package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
  129. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.d.ts.map +1 -1
  130. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +13 -8
  131. package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js.map +1 -1
  132. package/dist/planmagnet/pages/ProjectsPlanningPagePage.d.ts.map +1 -1
  133. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +76 -56
  134. package/dist/planmagnet/pages/ProjectsPlanningPagePage.js.map +1 -1
  135. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
  136. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +372 -203
  137. package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
  138. package/dist/planmagnet/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
  139. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js +81 -72
  140. package/dist/planmagnet/pages/ProjectsProjectsPagePage.js.map +1 -1
  141. package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts +2 -0
  142. package/dist/planmagnet/pages/RisksRiskDetailPagePage.d.ts.map +1 -0
  143. package/dist/planmagnet/pages/RisksRiskDetailPagePage.js +278 -0
  144. package/dist/planmagnet/pages/RisksRiskDetailPagePage.js.map +1 -0
  145. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts +2 -0
  146. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.d.ts.map +1 -0
  147. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js +191 -0
  148. package/dist/planmagnet/pages/RisksRiskRegisterPagePage.js.map +1 -0
  149. package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
  150. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +168 -122
  151. package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
  152. package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +5 -5
  153. package/dist/planmagnet/pages/TasksEditTaskPagePage.js +4 -4
  154. package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
  155. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +161 -137
  156. package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
  157. package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
  158. package/dist/planmagnet/pages/TasksTasksPagePage.js +124 -113
  159. package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
  160. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts +2 -0
  161. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.d.ts.map +1 -0
  162. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js +263 -0
  163. package/dist/planmagnet/pages/TasksTimeEntriesPagePage.js.map +1 -0
  164. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts +2 -0
  165. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.d.ts.map +1 -0
  166. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js +254 -0
  167. package/dist/planmagnet/pages/TasksTimesheetDetailPagePage.js.map +1 -0
  168. package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts +2 -0
  169. package/dist/planmagnet/pages/TasksTimesheetsPagePage.d.ts.map +1 -0
  170. package/dist/planmagnet/pages/TasksTimesheetsPagePage.js +165 -0
  171. package/dist/planmagnet/pages/TasksTimesheetsPagePage.js.map +1 -0
  172. package/dist/planmagnet/pages/TeamCreateTeamPagePage.d.ts.map +1 -1
  173. package/dist/planmagnet/pages/TeamCreateTeamPagePage.js +288 -91
  174. package/dist/planmagnet/pages/TeamCreateTeamPagePage.js.map +1 -1
  175. package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts +2 -0
  176. package/dist/planmagnet/pages/TeamTeamDetailPagePage.d.ts.map +1 -0
  177. package/dist/planmagnet/pages/TeamTeamDetailPagePage.js +217 -0
  178. package/dist/planmagnet/pages/TeamTeamDetailPagePage.js.map +1 -0
  179. package/dist/planmagnet/pages/TeamTeamEditPagePage.d.ts +2 -0
  180. package/dist/planmagnet/pages/TeamTeamEditPagePage.d.ts.map +1 -0
  181. package/dist/planmagnet/pages/TeamTeamEditPagePage.js +211 -0
  182. package/dist/planmagnet/pages/TeamTeamEditPagePage.js.map +1 -0
  183. package/dist/planmagnet/pages/TeamTeamsListPagePage.d.ts +2 -0
  184. package/dist/planmagnet/pages/TeamTeamsListPagePage.d.ts.map +1 -0
  185. package/dist/planmagnet/pages/TeamTeamsListPagePage.js +118 -0
  186. package/dist/planmagnet/pages/TeamTeamsListPagePage.js.map +1 -0
  187. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts +2 -0
  188. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.d.ts.map +1 -0
  189. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js +316 -0
  190. package/dist/planmagnet/pages/TemplatesEditTemplatePagePage.js.map +1 -0
  191. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts +2 -0
  192. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.d.ts.map +1 -0
  193. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js +208 -0
  194. package/dist/planmagnet/pages/TemplatesTemplatesPagePage.js.map +1 -0
  195. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts +0 -5
  196. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.d.ts.map +1 -1
  197. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js +47 -89
  198. package/dist/planmagnet/pages/analytics/DeliveryHealthCards.js.map +1 -1
  199. package/dist/planmagnet/roadmaps/RoadmapControls.js +24 -24
  200. package/dist/planmagnet/sprints/SprintControls.d.ts +15 -3
  201. package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
  202. package/dist/planmagnet/sprints/SprintControls.js +293 -190
  203. package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
  204. package/dist/planmagnet/team/TeamControls.d.ts +31 -0
  205. package/dist/planmagnet/team/TeamControls.d.ts.map +1 -0
  206. package/dist/planmagnet/team/TeamControls.js +407 -0
  207. package/dist/planmagnet/team/TeamControls.js.map +1 -0
  208. package/dist/planmagnet/time/TaskTimePanel.d.ts +7 -0
  209. package/dist/planmagnet/time/TaskTimePanel.d.ts.map +1 -0
  210. package/dist/planmagnet/time/TaskTimePanel.js +104 -0
  211. package/dist/planmagnet/time/TaskTimePanel.js.map +1 -0
  212. package/dist/planmagnet/time/TimeEntryControls.d.ts +37 -0
  213. package/dist/planmagnet/time/TimeEntryControls.d.ts.map +1 -0
  214. package/dist/planmagnet/time/TimeEntryControls.js +338 -0
  215. package/dist/planmagnet/time/TimeEntryControls.js.map +1 -0
  216. package/dist/planmagnet/time/TimesheetControls.d.ts +18 -0
  217. package/dist/planmagnet/time/TimesheetControls.d.ts.map +1 -0
  218. package/dist/planmagnet/time/TimesheetControls.js +244 -0
  219. package/dist/planmagnet/time/TimesheetControls.js.map +1 -0
  220. package/dist/planmagnet/time/timeShared.d.ts +29 -0
  221. package/dist/planmagnet/time/timeShared.d.ts.map +1 -0
  222. package/dist/planmagnet/time/timeShared.js +34 -0
  223. package/dist/planmagnet/time/timeShared.js.map +1 -0
  224. package/dist/planmagnet/utils/rowNavigation.js +5 -2
  225. package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
  226. package/package.json +1 -1
@@ -0,0 +1,18 @@
1
+ /** Create a timesheet for a period. Entries the caller already logged inside that period are claimed by it. */
2
+ export declare function CreateTimesheetDialog({ refetchQueries, onCreated, }: {
3
+ refetchQueries: string[];
4
+ onCreated?: (id: string) => void;
5
+ }): import("react/jsx-runtime").JSX.Element;
6
+ /** Submit a DRAFT or REJECTED timesheet for approval. Its totals are snapshotted at that instant (service). */
7
+ export declare function SubmitTimesheetButton({ id, status, refetchQueries, }: {
8
+ id: string;
9
+ status: string;
10
+ refetchQueries: string[];
11
+ }): import("react/jsx-runtime").JSX.Element | null;
12
+ /** Approve / Reject, shown for a SUBMITTED timesheet. Reject asks for an optional reason. */
13
+ export declare function TimesheetApprovalActions({ id, status, refetchQueries, }: {
14
+ id: string;
15
+ status: string;
16
+ refetchQueries: string[];
17
+ }): import("react/jsx-runtime").JSX.Element | null;
18
+ //# sourceMappingURL=TimesheetControls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimesheetControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/time/TimesheetControls.tsx"],"names":[],"mappings":"AA+EA,+GAA+G;AAC/G,wBAAgB,qBAAqB,CAAC,EACpC,cAAc,EACd,SAAS,GACV,EAAE;IACD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,2CAyGA;AAED,+GAA+G;AAC/G,wBAAgB,qBAAqB,CAAC,EACpC,EAAE,EACF,MAAM,EACN,cAAc,GACf,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,kDA+BA;AAED,6FAA6F;AAC7F,wBAAgB,wBAAwB,CAAC,EACvC,EAAE,EACF,MAAM,EACN,cAAc,GACf,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,kDA+FA"}
@@ -0,0 +1,244 @@
1
+ import { throwOnError as e } from "../utils/mutationResult.js";
2
+ import { ConfirmButton as t, ErrorLine as n } from "../controls/ConfirmButton.js";
3
+ import { SUBMITTABLE_TIMESHEET_STATUSES as r, todayInputValue as i } from "./timeShared.js";
4
+ import { useState as a } from "react";
5
+ import { CalendarPlus as o, Check as s, X as c } from "lucide-react";
6
+ import { gql as l } from "@apollo/client";
7
+ import { useMutation as u } from "@apollo/client/react";
8
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
9
+ import { Button as p, Input as m, ResponsiveDialog as h, ResponsiveDialogContent as g, ResponsiveDialogDescription as _, ResponsiveDialogFooter as v, ResponsiveDialogHeader as y, ResponsiveDialogTitle as b, ResponsiveDialogTrigger as x } from "@burdenoff/fe-libs/ui";
10
+ //#region src/planmagnet/time/TimesheetControls.tsx
11
+ var S = l`
12
+ mutation TimesheetControlsCreate($input: PlanmagnetCreateTimesheetInput!) {
13
+ planmagnetCreateTimesheet(input: $input) {
14
+ id
15
+ periodStart
16
+ periodEnd
17
+ status
18
+ totalMinutes
19
+ billableMinutes
20
+ }
21
+ }
22
+ `, C = l`
23
+ mutation TimesheetControlsSubmit($id: ID!) {
24
+ planmagnetSubmitTimesheet(id: $id) {
25
+ id
26
+ status
27
+ totalMinutes
28
+ billableMinutes
29
+ }
30
+ }
31
+ `, w = l`
32
+ mutation TimesheetControlsApprove($id: ID!) {
33
+ planmagnetApproveTimesheet(id: $id) {
34
+ id
35
+ status
36
+ approverId
37
+ approvedAt
38
+ }
39
+ }
40
+ `, T = l`
41
+ mutation TimesheetControlsReject($id: ID!, $reason: String) {
42
+ planmagnetRejectTimesheet(id: $id, reason: $reason) {
43
+ id
44
+ status
45
+ rejectionReason
46
+ }
47
+ }
48
+ `, E = (e, t) => e instanceof Error ? e.message : t;
49
+ function D({ refetchQueries: t, onCreated: r }) {
50
+ let [s, c] = a(!1), [l, C] = a(i()), [w, T] = a(i()), [D, O] = a(null), [k, { loading: A }] = u(S, {
51
+ refetchQueries: t,
52
+ awaitRefetchQueries: !0
53
+ }), j = () => {
54
+ C(i()), T(i()), O(null);
55
+ }, M = l && w && w < l, N = !A && !!l && !!w && !M, P = async () => {
56
+ O(null);
57
+ try {
58
+ let t = (await e(k({ variables: { input: {
59
+ periodStart: l,
60
+ periodEnd: w
61
+ } } }))).data?.planmagnetCreateTimesheet.id;
62
+ c(!1), j(), t && r?.(t);
63
+ } catch (e) {
64
+ O(E(e, "The timesheet could not be created."));
65
+ }
66
+ };
67
+ return /* @__PURE__ */ f(h, {
68
+ open: s,
69
+ onOpenChange: (e) => {
70
+ c(e), e ? j() : O(null);
71
+ },
72
+ children: [/* @__PURE__ */ d(x, {
73
+ asChild: !0,
74
+ children: /* @__PURE__ */ f(p, {
75
+ size: "sm",
76
+ "data-testid": "new-timesheet-open",
77
+ children: [/* @__PURE__ */ d(o, { className: "mr-1 h-4 w-4" }), "New timesheet"]
78
+ })
79
+ }), /* @__PURE__ */ f(g, {
80
+ className: "max-w-lg",
81
+ children: [
82
+ /* @__PURE__ */ f(y, { children: [/* @__PURE__ */ d(b, { children: "New timesheet" }), /* @__PURE__ */ d(_, { children: "Any of your own time entries already logged inside this period are added to it." })] }),
83
+ /* @__PURE__ */ f("div", {
84
+ className: "space-y-3",
85
+ children: [
86
+ /* @__PURE__ */ f("div", {
87
+ className: "grid grid-cols-2 gap-3",
88
+ children: [/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
89
+ htmlFor: "new-timesheet-start",
90
+ className: "text-sm font-medium",
91
+ children: "Period start"
92
+ }), /* @__PURE__ */ d(m, {
93
+ id: "new-timesheet-start",
94
+ className: "mt-1",
95
+ type: "date",
96
+ value: l,
97
+ onChange: (e) => C(e.target.value)
98
+ })] }), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
99
+ htmlFor: "new-timesheet-end",
100
+ className: "text-sm font-medium",
101
+ children: "Period end"
102
+ }), /* @__PURE__ */ d(m, {
103
+ id: "new-timesheet-end",
104
+ className: "mt-1",
105
+ type: "date",
106
+ value: w,
107
+ onChange: (e) => T(e.target.value)
108
+ })] })]
109
+ }),
110
+ M && /* @__PURE__ */ d(n, { message: "The period end cannot be before the period start." }),
111
+ D && /* @__PURE__ */ d(n, { message: D })
112
+ ]
113
+ }),
114
+ /* @__PURE__ */ f(v, { children: [/* @__PURE__ */ d(p, {
115
+ variant: "outline",
116
+ onClick: () => c(!1),
117
+ children: "Cancel"
118
+ }), /* @__PURE__ */ d(p, {
119
+ onClick: () => void P(),
120
+ disabled: !N,
121
+ "data-testid": "new-timesheet-submit",
122
+ children: A ? "Creating…" : "Create timesheet"
123
+ })] })
124
+ ]
125
+ })]
126
+ });
127
+ }
128
+ function O({ id: i, status: o, refetchQueries: c }) {
129
+ let [l, { loading: p }] = u(C, {
130
+ refetchQueries: c,
131
+ awaitRefetchQueries: !0
132
+ }), [m, h] = a(null);
133
+ if (!r.has(o)) return null;
134
+ let g = async () => {
135
+ h(null);
136
+ try {
137
+ await e(l({ variables: { id: i } }));
138
+ } catch (e) {
139
+ h(E(e, "The timesheet could not be submitted."));
140
+ }
141
+ };
142
+ return /* @__PURE__ */ f("div", {
143
+ className: "flex flex-col items-end gap-1",
144
+ children: [/* @__PURE__ */ d(t, {
145
+ label: "Submit for approval",
146
+ icon: /* @__PURE__ */ d(s, { className: "mr-1 h-4 w-4" }),
147
+ title: "Submit this timesheet?",
148
+ description: "Its hours are locked for review. Entries in it can no longer be edited or deleted until it is approved or rejected.",
149
+ confirmLabel: "Submit",
150
+ disabled: p,
151
+ onConfirm: () => void g()
152
+ }), m && /* @__PURE__ */ d(n, { message: m })]
153
+ });
154
+ }
155
+ function k({ id: r, status: i, refetchQueries: o }) {
156
+ let [l, m] = u(w, {
157
+ refetchQueries: o,
158
+ awaitRefetchQueries: !0
159
+ }), [S, C] = u(T, {
160
+ refetchQueries: o,
161
+ awaitRefetchQueries: !0
162
+ }), [D, O] = a(!1), [k, A] = a(""), [j, M] = a(null);
163
+ if (i !== "SUBMITTED") return null;
164
+ let N = m.loading || C.loading, P = async () => {
165
+ M(null);
166
+ try {
167
+ await e(l({ variables: { id: r } }));
168
+ } catch (e) {
169
+ M(E(e, "The timesheet could not be approved."));
170
+ }
171
+ }, F = async () => {
172
+ M(null);
173
+ try {
174
+ await e(S({ variables: {
175
+ id: r,
176
+ reason: k.trim() || void 0
177
+ } })), O(!1), A("");
178
+ } catch (e) {
179
+ M(E(e, "The timesheet could not be rejected."));
180
+ }
181
+ };
182
+ return /* @__PURE__ */ f("div", {
183
+ className: "flex flex-col items-end gap-1",
184
+ children: [/* @__PURE__ */ f("div", {
185
+ className: "flex gap-2",
186
+ children: [/* @__PURE__ */ d(t, {
187
+ label: "Approve",
188
+ icon: /* @__PURE__ */ d(s, { className: "mr-1 h-4 w-4" }),
189
+ title: "Approve this timesheet?",
190
+ description: "This is a billing sign-off on the hours shown. It cannot be undone here.",
191
+ confirmLabel: "Approve",
192
+ disabled: N,
193
+ onConfirm: () => void P()
194
+ }), /* @__PURE__ */ f(h, {
195
+ open: D,
196
+ onOpenChange: (e) => {
197
+ O(e), e || A("");
198
+ },
199
+ children: [/* @__PURE__ */ d(x, {
200
+ asChild: !0,
201
+ children: /* @__PURE__ */ f(p, {
202
+ size: "sm",
203
+ variant: "outline",
204
+ disabled: N,
205
+ "data-testid": "reject-timesheet-open",
206
+ children: [/* @__PURE__ */ d(c, { className: "mr-1 h-4 w-4" }), "Reject"]
207
+ })
208
+ }), /* @__PURE__ */ f(g, {
209
+ className: "max-w-md",
210
+ children: [
211
+ /* @__PURE__ */ f(y, { children: [/* @__PURE__ */ d(b, { children: "Reject this timesheet?" }), /* @__PURE__ */ d(_, { children: "It goes back to the owner as Rejected. They can fix the entries and re-submit." })] }),
212
+ /* @__PURE__ */ f("div", {
213
+ className: "space-y-2",
214
+ children: [/* @__PURE__ */ d("label", {
215
+ htmlFor: "reject-timesheet-reason",
216
+ className: "text-sm font-medium",
217
+ children: "Reason (optional, shown to the owner)"
218
+ }), /* @__PURE__ */ d("textarea", {
219
+ id: "reject-timesheet-reason",
220
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]",
221
+ value: k,
222
+ onChange: (e) => A(e.target.value)
223
+ })]
224
+ }),
225
+ /* @__PURE__ */ f(v, { children: [/* @__PURE__ */ d(p, {
226
+ variant: "outline",
227
+ onClick: () => O(!1),
228
+ children: "Cancel"
229
+ }), /* @__PURE__ */ d(p, {
230
+ onClick: () => void F(),
231
+ disabled: N,
232
+ "data-testid": "reject-timesheet-submit",
233
+ children: C.loading ? "Rejecting…" : "Reject"
234
+ })] })
235
+ ]
236
+ })]
237
+ })]
238
+ }), j && /* @__PURE__ */ d(n, { message: j })]
239
+ });
240
+ }
241
+ //#endregion
242
+ export { D as CreateTimesheetDialog, O as SubmitTimesheetButton, k as TimesheetApprovalActions };
243
+
244
+ //# sourceMappingURL=TimesheetControls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimesheetControls.js","names":[],"sources":["../../../src/planmagnet/time/TimesheetControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { CalendarPlus, Check, X } from 'lucide-react';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\nimport { SUBMITTABLE_TIMESHEET_STATUSES, todayInputValue } from './timeShared';\n\n/**\n * Timesheet controls (BOFF-8047): create a timesheet for a period (it claims the caller's own unattached\n * entries dated inside that period — service-side, `TimeTrackingService.createTimesheet`), submit it for\n * approval, and the approver's Approve / Reject.\n *\n * The service scopes every one of these to the caller's OWN workspace (BOFF-7915: they used to key on `{id}`\n * alone, so a caller holding `approve` in workspace A could act on workspace B's timesheet by id — fixed\n * `ab24587`) and to the `@rbac` action on the mutation (`approve` for Approve/Reject, `update` for Submit).\n * This UI does not attempt to guess who may approve: the buttons are offered to every viewer of a submitted\n * timesheet, and a caller without the `approve` permission gets the gateway's refusal on screen, the same way\n * every other permission-gated action in this MFE behaves (e.g. `WorkItemStatusSelect`).\n */\n\nconst CREATE_TIMESHEET = gql`\n mutation TimesheetControlsCreate($input: PlanmagnetCreateTimesheetInput!) {\n planmagnetCreateTimesheet(input: $input) {\n id\n periodStart\n periodEnd\n status\n totalMinutes\n billableMinutes\n }\n }\n`;\n\nconst SUBMIT_TIMESHEET = gql`\n mutation TimesheetControlsSubmit($id: ID!) {\n planmagnetSubmitTimesheet(id: $id) {\n id\n status\n totalMinutes\n billableMinutes\n }\n }\n`;\n\nconst APPROVE_TIMESHEET = gql`\n mutation TimesheetControlsApprove($id: ID!) {\n planmagnetApproveTimesheet(id: $id) {\n id\n status\n approverId\n approvedAt\n }\n }\n`;\n\nconst REJECT_TIMESHEET = gql`\n mutation TimesheetControlsReject($id: ID!, $reason: String) {\n planmagnetRejectTimesheet(id: $id, reason: $reason) {\n id\n status\n rejectionReason\n }\n }\n`;\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\n\n/** Create a timesheet for a period. Entries the caller already logged inside that period are claimed by it. */\nexport function CreateTimesheetDialog({\n refetchQueries,\n onCreated,\n}: {\n refetchQueries: string[];\n onCreated?: (id: string) => void;\n}) {\n const [open, setOpen] = useState(false);\n const [periodStart, setPeriodStart] = useState(todayInputValue());\n const [periodEnd, setPeriodEnd] = useState(todayInputValue());\n const [error, setError] = useState<string | null>(null);\n const [create, { loading }] = useMutation<{ planmagnetCreateTimesheet: { id: string } }>(\n CREATE_TIMESHEET,\n { refetchQueries, awaitRefetchQueries: true }\n );\n\n const reset = () => {\n setPeriodStart(todayInputValue());\n setPeriodEnd(todayInputValue());\n setError(null);\n };\n\n // Same-day rule the service applies (BOFF-7833, exact instants): a period ending before it starts is\n // refused, but an equal start/end day (a one-day timesheet) is valid.\n const invertedLocally = periodStart && periodEnd && periodEnd < periodStart;\n const canSubmit = !loading && !!periodStart && !!periodEnd && !invertedLocally;\n\n const submit = async () => {\n setError(null);\n try {\n const result = await throwOnError(\n create({ variables: { input: { periodStart, periodEnd } } })\n );\n const id = result.data?.planmagnetCreateTimesheet.id;\n setOpen(false);\n reset();\n if (id) onCreated?.(id);\n } catch (e) {\n setError(messageOf(e, 'The timesheet could not be created.'));\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) reset();\n else setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" data-testid=\"new-timesheet-open\">\n <CalendarPlus className=\"mr-1 h-4 w-4\" />\n New timesheet\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New timesheet</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Any of your own time entries already logged inside this period are added to it.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"new-timesheet-start\" className=\"text-sm font-medium\">\n Period start\n </label>\n <Input\n id=\"new-timesheet-start\"\n className=\"mt-1\"\n type=\"date\"\n value={periodStart}\n onChange={(e) => setPeriodStart(e.target.value)}\n />\n </div>\n <div>\n <label htmlFor=\"new-timesheet-end\" className=\"text-sm font-medium\">\n Period end\n </label>\n <Input\n id=\"new-timesheet-end\"\n className=\"mt-1\"\n type=\"date\"\n value={periodEnd}\n onChange={(e) => setPeriodEnd(e.target.value)}\n />\n </div>\n </div>\n {invertedLocally && (\n <ErrorLine message=\"The period end cannot be before the period start.\" />\n )}\n {error && <ErrorLine message={error} />}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button\n onClick={() => void submit()}\n disabled={!canSubmit}\n data-testid=\"new-timesheet-submit\"\n >\n {loading ? 'Creating…' : 'Create timesheet'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n/** Submit a DRAFT or REJECTED timesheet for approval. Its totals are snapshotted at that instant (service). */\nexport function SubmitTimesheetButton({\n id,\n status,\n refetchQueries,\n}: {\n id: string;\n status: string;\n refetchQueries: string[];\n}) {\n const [submitMutation, { loading }] = useMutation(SUBMIT_TIMESHEET, {\n refetchQueries,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n if (!SUBMITTABLE_TIMESHEET_STATUSES.has(status)) return null;\n\n const onSubmit = async () => {\n setError(null);\n try {\n await throwOnError(submitMutation({ variables: { id } }));\n } catch (e) {\n setError(messageOf(e, 'The timesheet could not be submitted.'));\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <ConfirmButton\n label=\"Submit for approval\"\n icon={<Check className=\"mr-1 h-4 w-4\" />}\n title=\"Submit this timesheet?\"\n description=\"Its hours are locked for review. Entries in it can no longer be edited or deleted until it is approved or rejected.\"\n confirmLabel=\"Submit\"\n disabled={loading}\n onConfirm={() => void onSubmit()}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** Approve / Reject, shown for a SUBMITTED timesheet. Reject asks for an optional reason. */\nexport function TimesheetApprovalActions({\n id,\n status,\n refetchQueries,\n}: {\n id: string;\n status: string;\n refetchQueries: string[];\n}) {\n const [approveMutation, approveState] = useMutation(APPROVE_TIMESHEET, {\n refetchQueries,\n awaitRefetchQueries: true,\n });\n const [rejectMutation, rejectState] = useMutation(REJECT_TIMESHEET, {\n refetchQueries,\n awaitRefetchQueries: true,\n });\n const [rejectOpen, setRejectOpen] = useState(false);\n const [reason, setReason] = useState('');\n const [error, setError] = useState<string | null>(null);\n if (status !== 'SUBMITTED') return null;\n const busy = approveState.loading || rejectState.loading;\n\n const onApprove = async () => {\n setError(null);\n try {\n await throwOnError(approveMutation({ variables: { id } }));\n } catch (e) {\n setError(messageOf(e, 'The timesheet could not be approved.'));\n }\n };\n const onReject = async () => {\n setError(null);\n try {\n await throwOnError(rejectMutation({ variables: { id, reason: reason.trim() || undefined } }));\n setRejectOpen(false);\n setReason('');\n } catch (e) {\n setError(messageOf(e, 'The timesheet could not be rejected.'));\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <div className=\"flex gap-2\">\n <ConfirmButton\n label=\"Approve\"\n icon={<Check className=\"mr-1 h-4 w-4\" />}\n title=\"Approve this timesheet?\"\n description=\"This is a billing sign-off on the hours shown. It cannot be undone here.\"\n confirmLabel=\"Approve\"\n disabled={busy}\n onConfirm={() => void onApprove()}\n />\n <ResponsiveDialog\n open={rejectOpen}\n onOpenChange={(next) => {\n setRejectOpen(next);\n if (!next) setReason('');\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" disabled={busy} data-testid=\"reject-timesheet-open\">\n <X className=\"mr-1 h-4 w-4\" />\n Reject\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Reject this timesheet?</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n It goes back to the owner as Rejected. They can fix the entries and re-submit.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-2\">\n <label htmlFor=\"reject-timesheet-reason\" className=\"text-sm font-medium\">\n Reason (optional, shown to the owner)\n </label>\n <textarea\n id=\"reject-timesheet-reason\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm min-h-[72px]\"\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n />\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setRejectOpen(false)}>\n Cancel\n </Button>\n <Button\n onClick={() => void onReject()}\n disabled={busy}\n data-testid=\"reject-timesheet-submit\"\n >\n {rejectState.loading ? 'Rejecting…' : 'Reject'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared constants and formatting for time tracking (BOFF-8047): time entries and
3
+ * timesheets. The service speaks whole minutes (`durationMinutes`) over the wire —
4
+ * `PlanmagnetTimeEntry.durationMinutes` / `PlanmagnetTimesheet.totalMinutes` /
5
+ * `billableMinutes` — even though the column underneath is decimal hours
6
+ * (wspace-planmagnet-svc `timetracking.resolvers.ts`); the UI only ever deals in
7
+ * minutes and hours-for-display, never the raw column.
8
+ */
9
+ export declare const TIME_ENTRY_STATUSES: readonly ["DRAFT", "SUBMITTED", "APPROVED", "REJECTED"];
10
+ export type TimeEntryStatus = (typeof TIME_ENTRY_STATUSES)[number];
11
+ export declare const BILLING_TYPES: readonly ["BILLABLE", "NON_BILLABLE", "INTERNAL"];
12
+ export type BillingType = (typeof BILLING_TYPES)[number];
13
+ /** A timesheet in this state is frozen: its entries cannot be edited, deleted or moved (service-enforced). */
14
+ export declare const FROZEN_TIMESHEET_STATUSES: Set<string>;
15
+ /** A timesheet can be (re-)submitted from these states. */
16
+ export declare const SUBMITTABLE_TIMESHEET_STATUSES: Set<string>;
17
+ export { enumLabel } from '../controls/WorkItemControls';
18
+ /** Whole minutes, read naturally as "1h 30m" / "45m" / "3h". */
19
+ export declare function formatMinutes(minutes: number): string;
20
+ /** Hours (may be fractional) for a numeric-input field, from whole minutes. */
21
+ export declare function minutesToHoursInput(minutes: number): string;
22
+ /** Whole minutes from an hours input string (e.g. "1.5" -> 90). Not finite/positive -> 0. */
23
+ export declare function hoursInputToMinutes(hours: string): number;
24
+ /** An ISO datetime (or date) string -> the `YYYY-MM-DD` an `<input type="date">` needs. UTC, matching the
25
+ * DateTime scalar's own "date-only = UTC midnight" contract, so re-sending it round-trips exactly. */
26
+ export declare function toDateInputValue(iso: string | null | undefined): string;
27
+ /** Today, as a `YYYY-MM-DD` UTC day — the default date for a new time entry / expense. */
28
+ export declare function todayInputValue(): string;
29
+ //# sourceMappingURL=timeShared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeShared.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/time/timeShared.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,eAAO,MAAM,mBAAmB,yDAA0D,CAAC;AAC3F,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,eAAO,MAAM,aAAa,mDAAoD,CAAC;AAC/E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,8GAA8G;AAC9G,eAAO,MAAM,yBAAyB,aAA6C,CAAC;AAEpF,2DAA2D;AAC3D,eAAO,MAAM,8BAA8B,aAAyC,CAAC;AAIrF,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAEzD,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,6FAA6F;AAC7F,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;sGACsG;AACtG,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAEvE;AAED,0FAA0F;AAC1F,wBAAgB,eAAe,IAAI,MAAM,CAExC"}
@@ -0,0 +1,34 @@
1
+ import "../controls/WorkItemControls.js";
2
+ //#region src/planmagnet/time/timeShared.ts
3
+ var e = [
4
+ "DRAFT",
5
+ "SUBMITTED",
6
+ "APPROVED",
7
+ "REJECTED"
8
+ ], t = [
9
+ "BILLABLE",
10
+ "NON_BILLABLE",
11
+ "INTERNAL"
12
+ ], n = new Set(["SUBMITTED", "APPROVED"]), r = new Set(["DRAFT", "REJECTED"]);
13
+ function i(e) {
14
+ if (!Number.isFinite(e) || e <= 0) return "0m";
15
+ let t = Math.floor(e / 60), n = Math.round(e % 60);
16
+ return t === 0 ? `${n}m` : n === 0 ? `${t}h` : `${t}h ${n}m`;
17
+ }
18
+ function a(e) {
19
+ return (e / 60).toString();
20
+ }
21
+ function o(e) {
22
+ let t = Number(e);
23
+ return Number.isFinite(t) && t > 0 ? Math.round(t * 60) : 0;
24
+ }
25
+ function s(e) {
26
+ return e ? e.slice(0, 10) : "";
27
+ }
28
+ function c() {
29
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
30
+ }
31
+ //#endregion
32
+ export { t as BILLING_TYPES, n as FROZEN_TIMESHEET_STATUSES, r as SUBMITTABLE_TIMESHEET_STATUSES, e as TIME_ENTRY_STATUSES, i as formatMinutes, o as hoursInputToMinutes, a as minutesToHoursInput, s as toDateInputValue, c as todayInputValue };
33
+
34
+ //# sourceMappingURL=timeShared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeShared.js","names":[],"sources":["../../../src/planmagnet/time/timeShared.ts"],"sourcesContent":["/**\n * Shared constants and formatting for time tracking (BOFF-8047): time entries and\n * timesheets. The service speaks whole minutes (`durationMinutes`) over the wire —\n * `PlanmagnetTimeEntry.durationMinutes` / `PlanmagnetTimesheet.totalMinutes` /\n * `billableMinutes` — even though the column underneath is decimal hours\n * (wspace-planmagnet-svc `timetracking.resolvers.ts`); the UI only ever deals in\n * minutes and hours-for-display, never the raw column.\n */\n\nexport const TIME_ENTRY_STATUSES = ['DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED'] as const;\nexport type TimeEntryStatus = (typeof TIME_ENTRY_STATUSES)[number];\n\nexport const BILLING_TYPES = ['BILLABLE', 'NON_BILLABLE', 'INTERNAL'] as const;\nexport type BillingType = (typeof BILLING_TYPES)[number];\n\n/** A timesheet in this state is frozen: its entries cannot be edited, deleted or moved (service-enforced). */\nexport const FROZEN_TIMESHEET_STATUSES = new Set<string>(['SUBMITTED', 'APPROVED']);\n\n/** A timesheet can be (re-)submitted from these states. */\nexport const SUBMITTABLE_TIMESHEET_STATUSES = new Set<string>(['DRAFT', 'REJECTED']);\n\n// `enumLabel` (e.g. \"NON_BILLABLE\" -> \"Non billable\") lives in controls/WorkItemControls —\n// reused here rather than redefined, so every enum in the MFE reads the same way.\nexport { enumLabel } from '../controls/WorkItemControls';\n\n/** Whole minutes, read naturally as \"1h 30m\" / \"45m\" / \"3h\". */\nexport function formatMinutes(minutes: number): string {\n if (!Number.isFinite(minutes) || minutes <= 0) return '0m';\n const h = Math.floor(minutes / 60);\n const m = Math.round(minutes % 60);\n if (h === 0) return `${m}m`;\n if (m === 0) return `${h}h`;\n return `${h}h ${m}m`;\n}\n\n/** Hours (may be fractional) for a numeric-input field, from whole minutes. */\nexport function minutesToHoursInput(minutes: number): string {\n return (minutes / 60).toString();\n}\n\n/** Whole minutes from an hours input string (e.g. \"1.5\" -> 90). Not finite/positive -> 0. */\nexport function hoursInputToMinutes(hours: string): number {\n const n = Number(hours);\n return Number.isFinite(n) && n > 0 ? Math.round(n * 60) : 0;\n}\n\n/** An ISO datetime (or date) string -> the `YYYY-MM-DD` an `<input type=\"date\">` needs. UTC, matching the\n * DateTime scalar's own \"date-only = UTC midnight\" contract, so re-sending it round-trips exactly. */\nexport function toDateInputValue(iso: string | null | undefined): string {\n return iso ? iso.slice(0, 10) : '';\n}\n\n/** Today, as a `YYYY-MM-DD` UTC day — the default date for a new time entry / expense. */\nexport function todayInputValue(): string {\n return new Date().toISOString().slice(0, 10);\n}\n"],"mappings":";;AASA,IAAa,IAAsB;CAAC;CAAS;CAAa;CAAY;CAAW,EAGpE,IAAgB;CAAC;CAAY;CAAgB;CAAW,EAIxD,IAA4B,IAAI,IAAY,CAAC,aAAa,WAAW,CAAC,EAGtE,IAAiC,IAAI,IAAY,CAAC,SAAS,WAAW,CAAC;AAOpF,SAAgB,EAAc,GAAyB;AACrD,KAAI,CAAC,OAAO,SAAS,EAAQ,IAAI,KAAW,EAAG,QAAO;CACtD,IAAM,IAAI,KAAK,MAAM,IAAU,GAAG,EAC5B,IAAI,KAAK,MAAM,IAAU,GAAG;AAGlC,QAFI,MAAM,IAAU,GAAG,EAAE,KACrB,MAAM,IAAU,GAAG,EAAE,KAClB,GAAG,EAAE,IAAI,EAAE;;AAIpB,SAAgB,EAAoB,GAAyB;AAC3D,SAAQ,IAAU,IAAI,UAAU;;AAIlC,SAAgB,EAAoB,GAAuB;CACzD,IAAM,IAAI,OAAO,EAAM;AACvB,QAAO,OAAO,SAAS,EAAE,IAAI,IAAI,IAAI,KAAK,MAAM,IAAI,GAAG,GAAG;;AAK5D,SAAgB,EAAiB,GAAwC;AACvE,QAAO,IAAM,EAAI,MAAM,GAAG,GAAG,GAAG;;AAIlC,SAAgB,IAA0B;AACxC,yBAAO,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG"}
@@ -12,10 +12,13 @@ function t(t, n) {
12
12
  }
13
13
  };
14
14
  }
15
- function n(e, t) {
15
+ function n(e) {
16
+ e.stopPropagation();
17
+ }
18
+ function r(e, t) {
16
19
  return t ? `${e}/${encodeURIComponent(t)}` : e;
17
20
  }
18
21
  //#endregion
19
- export { e as ROW_LINK_CLASS, n as detailPath, t as rowLinkProps };
22
+ export { e as ROW_LINK_CLASS, r as detailPath, t as rowLinkProps, n as stopRowActivation };
20
23
 
21
24
  //# sourceMappingURL=rowNavigation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rowNavigation.js","names":[],"sources":["../../../src/planmagnet/utils/rowNavigation.ts"],"sourcesContent":["import type { KeyboardEvent, MouseEvent } from 'react';\n\n/**\n * Props that turn a non-interactive container (a Card, a table row) into an\n * activatable one, so a list row can open its detail page by click OR keyboard.\n *\n * Kept as a props factory rather than a wrapper component so it can be spread onto\n * whatever element a given list already renders, without changing that list's layout.\n */\n/** The row-link look, for rows that also need their own layout classes. */\nexport const ROW_LINK_CLASS =\n 'cursor-pointer transition-shadow hover:shadow-md focus-visible:outline-none ' +\n 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2';\n\nexport function rowLinkProps(onActivate: () => void, label?: string) {\n return {\n role: 'button' as const,\n tabIndex: 0,\n 'aria-label': label,\n className: ROW_LINK_CLASS,\n onClick: () => onActivate(),\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => {\n // Enter and Space are the two activation keys a role=\"button\" must honour.\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n onActivate();\n },\n };\n}\n\n/**\n * Stops a click on a nested control (a button, a menu) from also activating the row\n * it sits inside. Spread onto the nested control, not the row.\n */\nexport function stopRowActivation(event: MouseEvent<HTMLElement>) {\n event.stopPropagation();\n}\n\n/**\n * Builds the path to a detail page that reads its id from a \":id\" route segment.\n * Returns the bare path when no id is available, so a missing id degrades to the\n * list-style page rather than producing a path ending in \"undefined\".\n */\nexport function detailPath(route: string, id: string | null | undefined): string {\n return id ? `${route}/${encodeURIComponent(id)}` : route;\n}\n"],"mappings":";AAUA,IAAa,IACX;AAGF,SAAgB,EAAa,GAAwB,GAAgB;AACnE,QAAO;EACL,MAAM;EACN,UAAU;EACV,cAAc;EACd,WAAW;EACX,eAAe,GAAY;EAC3B,YAAY,MAAsC;AAE5C,KAAM,QAAQ,WAAW,EAAM,QAAQ,QAC3C,EAAM,gBAAgB,EACtB,GAAY;;EAEf;;AAgBH,SAAgB,EAAW,GAAe,GAAuC;AAC/E,QAAO,IAAK,GAAG,EAAM,GAAG,mBAAmB,EAAG,KAAK"}
1
+ {"version":3,"file":"rowNavigation.js","names":[],"sources":["../../../src/planmagnet/utils/rowNavigation.ts"],"sourcesContent":["import type { KeyboardEvent, MouseEvent } from 'react';\n\n/**\n * Props that turn a non-interactive container (a Card, a table row) into an\n * activatable one, so a list row can open its detail page by click OR keyboard.\n *\n * Kept as a props factory rather than a wrapper component so it can be spread onto\n * whatever element a given list already renders, without changing that list's layout.\n */\n/** The row-link look, for rows that also need their own layout classes. */\nexport const ROW_LINK_CLASS =\n 'cursor-pointer transition-shadow hover:shadow-md focus-visible:outline-none ' +\n 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2';\n\nexport function rowLinkProps(onActivate: () => void, label?: string) {\n return {\n role: 'button' as const,\n tabIndex: 0,\n 'aria-label': label,\n className: ROW_LINK_CLASS,\n onClick: () => onActivate(),\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => {\n // Enter and Space are the two activation keys a role=\"button\" must honour.\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n onActivate();\n },\n };\n}\n\n/**\n * Stops a click on a nested control (a button, a menu) from also activating the row\n * it sits inside. Spread onto the nested control, not the row.\n */\nexport function stopRowActivation(event: MouseEvent<HTMLElement>) {\n event.stopPropagation();\n}\n\n/**\n * Builds the path to a detail page that reads its id from a \":id\" route segment.\n * Returns the bare path when no id is available, so a missing id degrades to the\n * list-style page rather than producing a path ending in \"undefined\".\n */\nexport function detailPath(route: string, id: string | null | undefined): string {\n return id ? `${route}/${encodeURIComponent(id)}` : route;\n}\n"],"mappings":";AAUA,IAAa,IACX;AAGF,SAAgB,EAAa,GAAwB,GAAgB;AACnE,QAAO;EACL,MAAM;EACN,UAAU;EACV,cAAc;EACd,WAAW;EACX,eAAe,GAAY;EAC3B,YAAY,MAAsC;AAE5C,KAAM,QAAQ,WAAW,EAAM,QAAQ,QAC3C,EAAM,gBAAgB,EACtB,GAAY;;EAEf;;AAOH,SAAgB,EAAkB,GAAgC;AAChE,GAAM,iBAAiB;;AAQzB,SAAgB,EAAW,GAAe,GAAuC;AAC/E,QAAO,IAAK,GAAG,EAAM,GAAG,mBAAmB,EAAG,KAAK"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-planmagnet",
3
- "version": "2026.928.4",
3
+ "version": "2026.928.6",
4
4
  "description": "Burdenoff PlanMagnet Microfrontend",
5
5
  "type": "module",
6
6
  "files": [