@burdenoff/microfe-planmagnet 2026.928.3 → 2026.928.5
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 +126 -84
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +192 -64
- 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.d.ts +20 -0
- package/dist/planmagnet/controls/CompetitorControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/CompetitorControls.js +172 -0
- package/dist/planmagnet/controls/CompetitorControls.js.map +1 -0
- package/dist/planmagnet/controls/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 +53 -53
- package/dist/planmagnet/controls/FeatureTriage.js +11 -11
- package/dist/planmagnet/controls/ObjectiveControls.js +37 -37
- package/dist/planmagnet/controls/Pickers.d.ts +17 -2
- package/dist/planmagnet/controls/Pickers.d.ts.map +1 -1
- package/dist/planmagnet/controls/Pickers.js +100 -9
- package/dist/planmagnet/controls/Pickers.js.map +1 -1
- package/dist/planmagnet/controls/PortfolioControls.d.ts +13 -0
- package/dist/planmagnet/controls/PortfolioControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/PortfolioControls.js +114 -0
- package/dist/planmagnet/controls/PortfolioControls.js.map +1 -0
- package/dist/planmagnet/controls/ProgramControls.d.ts +25 -0
- package/dist/planmagnet/controls/ProgramControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/ProgramControls.js +187 -0
- package/dist/planmagnet/controls/ProgramControls.js.map +1 -0
- package/dist/planmagnet/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.js +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/CompetitorsCompetitorDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js +127 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js +129 -0
- package/dist/planmagnet/pages/CompetitorsCompetitorsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js +132 -0
- package/dist/planmagnet/pages/CompetitorsCreateCompetitorPagePage.js.map +1 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js +181 -0
- package/dist/planmagnet/pages/CompetitorsEditCompetitorPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.js +242 -0
- package/dist/planmagnet/pages/PortfoliosCreatePortfolioPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js +302 -0
- package/dist/planmagnet/pages/PortfoliosEditPortfolioPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js +335 -0
- package/dist/planmagnet/pages/PortfoliosPortfolioDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js +154 -0
- package/dist/planmagnet/pages/PortfoliosPortfoliosPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +124 -138
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js +221 -0
- package/dist/planmagnet/pages/ProgramsCreateProgramPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js +279 -0
- package/dist/planmagnet/pages/ProgramsEditProgramPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.js +178 -0
- package/dist/planmagnet/pages/ProgramsProgramDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.js +157 -0
- package/dist/planmagnet/pages/ProgramsProgramsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/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.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +130 -103
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +4 -4
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +6 -6
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +325 -189
- 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/TasksCreateTaskPagePage.js +5 -5
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js +4 -4
- package/dist/planmagnet/pages/TasksImportPagePage.js +5 -5
- 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/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/time/TaskTimePanel.d.ts +7 -0
- package/dist/planmagnet/time/TaskTimePanel.d.ts.map +1 -0
- package/dist/planmagnet/time/TaskTimePanel.js +104 -0
- package/dist/planmagnet/time/TaskTimePanel.js.map +1 -0
- package/dist/planmagnet/time/TimeEntryControls.d.ts +37 -0
- package/dist/planmagnet/time/TimeEntryControls.d.ts.map +1 -0
- package/dist/planmagnet/time/TimeEntryControls.js +338 -0
- package/dist/planmagnet/time/TimeEntryControls.js.map +1 -0
- package/dist/planmagnet/time/TimesheetControls.d.ts +18 -0
- package/dist/planmagnet/time/TimesheetControls.d.ts.map +1 -0
- package/dist/planmagnet/time/TimesheetControls.js +244 -0
- package/dist/planmagnet/time/TimesheetControls.js.map +1 -0
- package/dist/planmagnet/time/timeShared.d.ts +29 -0
- package/dist/planmagnet/time/timeShared.d.ts.map +1 -0
- package/dist/planmagnet/time/timeShared.js +34 -0
- package/dist/planmagnet/time/timeShared.js.map +1 -0
- package/dist/planmagnet/utils/rowNavigation.js +5 -2
- package/dist/planmagnet/utils/rowNavigation.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimeEntryControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/time/TimeEntryControls.tsx"],"names":[],"mappings":"AAiFA,iHAAiH;AACjH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,mGAAmG;IACnG,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,cAAc,EACd,QAAQ,EACR,YAAyB,EACzB,WAAkB,GACnB,EAAE;IACD,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,4GAA4G;IAC5G,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;CAChC,2CAyLA;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,sGAAsG;AACtG,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,cAAc,GACf,EAAE;IACD,KAAK,EAAE,eAAe,CAAC;IACvB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,2CAuIA;AAED,8CAA8C;AAC9C,wBAAgB,qBAAqB,CAAC,EACpC,EAAE,EACF,cAAc,GACf,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,2CAiCA"}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { throwOnError as e } from "../utils/mutationResult.js";
|
|
2
|
+
import { ConfirmButton as t, ErrorLine as n } from "../controls/ConfirmButton.js";
|
|
3
|
+
import { ProjectSelect as r, WorkItemSearchSelect as i } from "../controls/Pickers.js";
|
|
4
|
+
import { enumLabel as a } from "../controls/WorkItemControls.js";
|
|
5
|
+
import { BILLING_TYPES as o, hoursInputToMinutes as s, minutesToHoursInput as c, toDateInputValue as l, todayInputValue as u } from "./timeShared.js";
|
|
6
|
+
import { useState as d } from "react";
|
|
7
|
+
import { Clock as f, Pencil as p, Trash2 as m } from "lucide-react";
|
|
8
|
+
import { gql as h } from "@apollo/client";
|
|
9
|
+
import { useMutation as g } from "@apollo/client/react";
|
|
10
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
11
|
+
import { Button as b, Input as x, ResponsiveDialog as S, ResponsiveDialogContent as C, ResponsiveDialogDescription as w, ResponsiveDialogFooter as T, ResponsiveDialogHeader as E, ResponsiveDialogTitle as D, ResponsiveDialogTrigger as O } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/planmagnet/time/TimeEntryControls.tsx
|
|
13
|
+
var k = h`
|
|
14
|
+
mutation TimeEntryControlsLogTime($input: PlanmagnetLogTimeInput!) {
|
|
15
|
+
planmagnetLogTime(input: $input) {
|
|
16
|
+
id
|
|
17
|
+
workItemId
|
|
18
|
+
projectId
|
|
19
|
+
timesheetId
|
|
20
|
+
description
|
|
21
|
+
durationMinutes
|
|
22
|
+
date
|
|
23
|
+
status
|
|
24
|
+
billingType
|
|
25
|
+
createdAt
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`, A = h`
|
|
29
|
+
mutation TimeEntryControlsUpdate($id: ID!, $input: PlanmagnetUpdateTimeEntryInput!) {
|
|
30
|
+
planmagnetUpdateTimeEntry(id: $id, input: $input) {
|
|
31
|
+
id
|
|
32
|
+
description
|
|
33
|
+
durationMinutes
|
|
34
|
+
date
|
|
35
|
+
billingType
|
|
36
|
+
updatedAt
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
`, j = h`
|
|
40
|
+
mutation TimeEntryControlsDelete($id: ID!) {
|
|
41
|
+
planmagnetDeleteTimeEntry(id: $id)
|
|
42
|
+
}
|
|
43
|
+
`, M = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60", N = (e, t) => e instanceof Error ? e.message : t;
|
|
44
|
+
function P({ lock: t, refetchQueries: c, onLogged: l, triggerLabel: p = "Log time", triggerSize: m = "sm" }) {
|
|
45
|
+
let [h, A] = d(!1), [j, P] = d(""), [F, I] = d(""), [L, R] = d(""), [z, B] = d(u()), [V, H] = d("NON_BILLABLE"), [U, W] = d(""), [G, K] = d(null), [q, { loading: J }] = g(k, {
|
|
46
|
+
refetchQueries: c,
|
|
47
|
+
awaitRefetchQueries: !0
|
|
48
|
+
}), Y = () => {
|
|
49
|
+
P(""), I(""), R(""), B(u()), H("NON_BILLABLE"), W(""), K(null);
|
|
50
|
+
}, X = t?.workItemId ?? F, Z = t?.projectId ?? j, Q = s(L), $ = !J && !!X && !!Z && Q > 0 && !!z, ee = async () => {
|
|
51
|
+
K(null);
|
|
52
|
+
try {
|
|
53
|
+
let t = (await e(q({ variables: { input: {
|
|
54
|
+
workItemId: X,
|
|
55
|
+
projectId: Z,
|
|
56
|
+
durationMinutes: Q,
|
|
57
|
+
date: z,
|
|
58
|
+
billingType: V,
|
|
59
|
+
...U.trim() ? { description: U.trim() } : {}
|
|
60
|
+
} } }))).data?.planmagnetLogTime.id;
|
|
61
|
+
A(!1), Y(), t && l?.(t);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
K(N(e, "The time could not be logged."));
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
return /* @__PURE__ */ y(S, {
|
|
67
|
+
open: h,
|
|
68
|
+
onOpenChange: (e) => {
|
|
69
|
+
A(e), e ? Y() : K(null);
|
|
70
|
+
},
|
|
71
|
+
children: [/* @__PURE__ */ v(O, {
|
|
72
|
+
asChild: !0,
|
|
73
|
+
children: /* @__PURE__ */ y(b, {
|
|
74
|
+
size: m,
|
|
75
|
+
"data-testid": "log-time-open",
|
|
76
|
+
children: [/* @__PURE__ */ v(f, { className: "mr-1 h-4 w-4" }), p]
|
|
77
|
+
})
|
|
78
|
+
}), /* @__PURE__ */ y(C, {
|
|
79
|
+
className: "max-w-lg",
|
|
80
|
+
children: [
|
|
81
|
+
/* @__PURE__ */ y(E, { children: [/* @__PURE__ */ v(D, { children: "Log time" }), /* @__PURE__ */ v(w, { children: t ? `Against ${t.label}.` : "Pick a project and a work item, then the time spent." })] }),
|
|
82
|
+
/* @__PURE__ */ y("div", {
|
|
83
|
+
className: "space-y-3",
|
|
84
|
+
children: [
|
|
85
|
+
!t && /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
86
|
+
htmlFor: "log-time-project",
|
|
87
|
+
className: "text-sm font-medium",
|
|
88
|
+
children: "Project"
|
|
89
|
+
}), /* @__PURE__ */ v("div", {
|
|
90
|
+
className: "mt-1",
|
|
91
|
+
children: /* @__PURE__ */ v(r, {
|
|
92
|
+
id: "log-time-project",
|
|
93
|
+
value: j,
|
|
94
|
+
onChange: (e) => {
|
|
95
|
+
P(e), I("");
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
})] }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
99
|
+
htmlFor: "log-time-work-item",
|
|
100
|
+
className: "text-sm font-medium",
|
|
101
|
+
children: "Work item"
|
|
102
|
+
}), /* @__PURE__ */ v("div", {
|
|
103
|
+
className: "mt-1",
|
|
104
|
+
children: /* @__PURE__ */ v(i, {
|
|
105
|
+
id: "log-time-work-item",
|
|
106
|
+
projectId: j || void 0,
|
|
107
|
+
value: F,
|
|
108
|
+
onChange: I
|
|
109
|
+
})
|
|
110
|
+
})] })] }),
|
|
111
|
+
/* @__PURE__ */ y("div", {
|
|
112
|
+
className: "grid grid-cols-2 gap-3",
|
|
113
|
+
children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
114
|
+
htmlFor: "log-time-hours",
|
|
115
|
+
className: "text-sm font-medium",
|
|
116
|
+
children: "Hours"
|
|
117
|
+
}), /* @__PURE__ */ v(x, {
|
|
118
|
+
id: "log-time-hours",
|
|
119
|
+
className: "mt-1",
|
|
120
|
+
type: "number",
|
|
121
|
+
min: "0",
|
|
122
|
+
step: "0.25",
|
|
123
|
+
inputMode: "decimal",
|
|
124
|
+
placeholder: "e.g. 1.5",
|
|
125
|
+
value: L,
|
|
126
|
+
onChange: (e) => R(e.target.value)
|
|
127
|
+
})] }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
128
|
+
htmlFor: "log-time-date",
|
|
129
|
+
className: "text-sm font-medium",
|
|
130
|
+
children: "Date"
|
|
131
|
+
}), /* @__PURE__ */ v(x, {
|
|
132
|
+
id: "log-time-date",
|
|
133
|
+
className: "mt-1",
|
|
134
|
+
type: "date",
|
|
135
|
+
value: z,
|
|
136
|
+
onChange: (e) => B(e.target.value)
|
|
137
|
+
})] })]
|
|
138
|
+
}),
|
|
139
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
140
|
+
htmlFor: "log-time-billing",
|
|
141
|
+
className: "text-sm font-medium",
|
|
142
|
+
children: "Billing type"
|
|
143
|
+
}), /* @__PURE__ */ v("select", {
|
|
144
|
+
id: "log-time-billing",
|
|
145
|
+
className: M,
|
|
146
|
+
value: V,
|
|
147
|
+
onChange: (e) => H(e.target.value),
|
|
148
|
+
children: o.map((e) => /* @__PURE__ */ v("option", {
|
|
149
|
+
value: e,
|
|
150
|
+
children: a(e)
|
|
151
|
+
}, e))
|
|
152
|
+
})] }),
|
|
153
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
154
|
+
htmlFor: "log-time-description",
|
|
155
|
+
className: "text-sm font-medium",
|
|
156
|
+
children: "Description"
|
|
157
|
+
}), /* @__PURE__ */ v("textarea", {
|
|
158
|
+
id: "log-time-description",
|
|
159
|
+
className: `${M} min-h-[64px]`,
|
|
160
|
+
placeholder: "What did you work on? (optional)",
|
|
161
|
+
value: U,
|
|
162
|
+
onChange: (e) => W(e.target.value)
|
|
163
|
+
})] }),
|
|
164
|
+
G && /* @__PURE__ */ v(n, { message: G })
|
|
165
|
+
]
|
|
166
|
+
}),
|
|
167
|
+
/* @__PURE__ */ y(T, { children: [/* @__PURE__ */ v(b, {
|
|
168
|
+
variant: "outline",
|
|
169
|
+
onClick: () => A(!1),
|
|
170
|
+
children: "Cancel"
|
|
171
|
+
}), /* @__PURE__ */ v(b, {
|
|
172
|
+
onClick: () => void ee(),
|
|
173
|
+
disabled: !$,
|
|
174
|
+
"data-testid": "log-time-submit",
|
|
175
|
+
children: J ? "Logging…" : "Log time"
|
|
176
|
+
})] })
|
|
177
|
+
]
|
|
178
|
+
})]
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function F({ entry: t, refetchQueries: r }) {
|
|
182
|
+
let i = {
|
|
183
|
+
description: t.description ?? "",
|
|
184
|
+
hours: c(t.durationMinutes),
|
|
185
|
+
date: l(t.date),
|
|
186
|
+
billingType: t.billingType
|
|
187
|
+
}, [u, f] = d(!1), [m, h] = d(i), [_, k] = d(null), [j, { loading: P }] = g(A, {
|
|
188
|
+
refetchQueries: r,
|
|
189
|
+
awaitRefetchQueries: !0
|
|
190
|
+
}), F = s(m.hours), I = !P && F > 0 && !!m.date, L = async () => {
|
|
191
|
+
k(null);
|
|
192
|
+
let n = {};
|
|
193
|
+
if (m.description.trim() !== i.description.trim() && (n.description = m.description.trim() || null), F !== t.durationMinutes && (n.durationMinutes = F), m.date !== i.date && (n.date = m.date), m.billingType !== i.billingType && (n.billingType = m.billingType), Object.keys(n).length === 0) {
|
|
194
|
+
f(!1);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
await e(j({ variables: {
|
|
199
|
+
id: t.id,
|
|
200
|
+
input: n
|
|
201
|
+
} })), f(!1);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
k(N(e, "The time entry could not be saved."));
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
return /* @__PURE__ */ y(S, {
|
|
207
|
+
open: u,
|
|
208
|
+
onOpenChange: (e) => {
|
|
209
|
+
f(e), e ? h(i) : k(null);
|
|
210
|
+
},
|
|
211
|
+
children: [/* @__PURE__ */ v(O, {
|
|
212
|
+
asChild: !0,
|
|
213
|
+
children: /* @__PURE__ */ v(b, {
|
|
214
|
+
size: "sm",
|
|
215
|
+
variant: "outline",
|
|
216
|
+
"aria-label": "Edit time entry",
|
|
217
|
+
"data-testid": "time-entry-edit",
|
|
218
|
+
children: /* @__PURE__ */ v(p, { className: "h-4 w-4" })
|
|
219
|
+
})
|
|
220
|
+
}), /* @__PURE__ */ y(C, {
|
|
221
|
+
className: "max-w-lg",
|
|
222
|
+
children: [
|
|
223
|
+
/* @__PURE__ */ y(E, { children: [/* @__PURE__ */ v(D, { children: "Edit time entry" }), /* @__PURE__ */ v(w, { children: "Change the hours, date, billing type or description." })] }),
|
|
224
|
+
/* @__PURE__ */ y("div", {
|
|
225
|
+
className: "space-y-3",
|
|
226
|
+
children: [
|
|
227
|
+
/* @__PURE__ */ y("div", {
|
|
228
|
+
className: "grid grid-cols-2 gap-3",
|
|
229
|
+
children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
230
|
+
htmlFor: "edit-time-hours",
|
|
231
|
+
className: "text-sm font-medium",
|
|
232
|
+
children: "Hours"
|
|
233
|
+
}), /* @__PURE__ */ v(x, {
|
|
234
|
+
id: "edit-time-hours",
|
|
235
|
+
className: "mt-1",
|
|
236
|
+
type: "number",
|
|
237
|
+
min: "0",
|
|
238
|
+
step: "0.25",
|
|
239
|
+
inputMode: "decimal",
|
|
240
|
+
value: m.hours,
|
|
241
|
+
onChange: (e) => h({
|
|
242
|
+
...m,
|
|
243
|
+
hours: e.target.value
|
|
244
|
+
})
|
|
245
|
+
})] }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
246
|
+
htmlFor: "edit-time-date",
|
|
247
|
+
className: "text-sm font-medium",
|
|
248
|
+
children: "Date"
|
|
249
|
+
}), /* @__PURE__ */ v(x, {
|
|
250
|
+
id: "edit-time-date",
|
|
251
|
+
className: "mt-1",
|
|
252
|
+
type: "date",
|
|
253
|
+
value: m.date,
|
|
254
|
+
onChange: (e) => h({
|
|
255
|
+
...m,
|
|
256
|
+
date: e.target.value
|
|
257
|
+
})
|
|
258
|
+
})] })]
|
|
259
|
+
}),
|
|
260
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
261
|
+
htmlFor: "edit-time-billing",
|
|
262
|
+
className: "text-sm font-medium",
|
|
263
|
+
children: "Billing type"
|
|
264
|
+
}), /* @__PURE__ */ v("select", {
|
|
265
|
+
id: "edit-time-billing",
|
|
266
|
+
className: M,
|
|
267
|
+
value: m.billingType,
|
|
268
|
+
onChange: (e) => h({
|
|
269
|
+
...m,
|
|
270
|
+
billingType: e.target.value
|
|
271
|
+
}),
|
|
272
|
+
children: o.map((e) => /* @__PURE__ */ v("option", {
|
|
273
|
+
value: e,
|
|
274
|
+
children: a(e)
|
|
275
|
+
}, e))
|
|
276
|
+
})] }),
|
|
277
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
278
|
+
htmlFor: "edit-time-description",
|
|
279
|
+
className: "text-sm font-medium",
|
|
280
|
+
children: "Description"
|
|
281
|
+
}), /* @__PURE__ */ v("textarea", {
|
|
282
|
+
id: "edit-time-description",
|
|
283
|
+
className: `${M} min-h-[64px]`,
|
|
284
|
+
value: m.description,
|
|
285
|
+
onChange: (e) => h({
|
|
286
|
+
...m,
|
|
287
|
+
description: e.target.value
|
|
288
|
+
})
|
|
289
|
+
})] }),
|
|
290
|
+
_ && /* @__PURE__ */ v(n, { message: _ })
|
|
291
|
+
]
|
|
292
|
+
}),
|
|
293
|
+
/* @__PURE__ */ y(T, { children: [/* @__PURE__ */ v(b, {
|
|
294
|
+
variant: "outline",
|
|
295
|
+
onClick: () => f(!1),
|
|
296
|
+
children: "Cancel"
|
|
297
|
+
}), /* @__PURE__ */ v(b, {
|
|
298
|
+
onClick: () => void L(),
|
|
299
|
+
disabled: !I,
|
|
300
|
+
"data-testid": "edit-time-save",
|
|
301
|
+
children: P ? "Saving…" : "Save"
|
|
302
|
+
})] })
|
|
303
|
+
]
|
|
304
|
+
})]
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
function I({ id: r, refetchQueries: i }) {
|
|
308
|
+
let [a, { loading: o }] = g(j, {
|
|
309
|
+
refetchQueries: i,
|
|
310
|
+
awaitRefetchQueries: !0
|
|
311
|
+
}), [s, c] = d(null), l = async () => {
|
|
312
|
+
c(null);
|
|
313
|
+
try {
|
|
314
|
+
await e(a({ variables: { id: r } }));
|
|
315
|
+
} catch (e) {
|
|
316
|
+
c(N(e, "The time entry could not be deleted."));
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
return /* @__PURE__ */ y("div", {
|
|
320
|
+
className: "flex flex-col items-end gap-1",
|
|
321
|
+
children: [/* @__PURE__ */ v(t, {
|
|
322
|
+
label: "",
|
|
323
|
+
ariaLabel: "Delete time entry",
|
|
324
|
+
variant: "outline",
|
|
325
|
+
icon: /* @__PURE__ */ v(m, { className: "h-4 w-4" }),
|
|
326
|
+
title: "Delete this time entry?",
|
|
327
|
+
description: "The logged time is removed. This cannot be undone here.",
|
|
328
|
+
confirmLabel: "Delete entry",
|
|
329
|
+
destructive: !0,
|
|
330
|
+
disabled: o,
|
|
331
|
+
onConfirm: () => void l()
|
|
332
|
+
}), s && /* @__PURE__ */ v(n, { message: s })]
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
export { I as DeleteTimeEntryButton, F as EditTimeEntryDialog, P as LogTimeDialog };
|
|
337
|
+
|
|
338
|
+
//# sourceMappingURL=TimeEntryControls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimeEntryControls.js","names":[],"sources":["../../../src/planmagnet/time/TimeEntryControls.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 { Clock, Pencil, Trash2 } from 'lucide-react';\nimport { ProjectSelect, WorkItemSearchSelect } from '../controls/Pickers';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\nimport {\n BILLING_TYPES,\n type BillingType,\n enumLabel,\n hoursInputToMinutes,\n minutesToHoursInput,\n todayInputValue,\n toDateInputValue,\n} from './timeShared';\n\n/**\n * Time entry controls (BOFF-8047): logging time, editing an entry, deleting one.\n *\n * `planmagnetLogTime` (not the lower-level `planmagnetCreateTimeEntry`) is the write path here — it is the\n * \"quick log time\" mutation the schema documents for this exact purpose, and it does not accept a billable\n * rate. That is deliberate, not a missing feature: \"A Delivery Contributor with EDITOR can read every time\n * entry in the workspace, including colleagues' billable rate and currency\" is a known, still-open RBAC gap\n * (planmagnet-specs/CORRECTIONS.md §B) — the resolver does not scope `billingRate` by role. Keeping rate\n * capture and display out of this UI entirely means this screen cannot become a new way to see it; fixing\n * the gap itself is a backend RBAC change, out of scope here (recorded as a backend gap).\n */\n\nconst LOG_TIME = gql`\n mutation TimeEntryControlsLogTime($input: PlanmagnetLogTimeInput!) {\n planmagnetLogTime(input: $input) {\n id\n workItemId\n projectId\n timesheetId\n description\n durationMinutes\n date\n status\n billingType\n createdAt\n }\n }\n`;\n\nconst UPDATE_TIME_ENTRY = gql`\n mutation TimeEntryControlsUpdate($id: ID!, $input: PlanmagnetUpdateTimeEntryInput!) {\n planmagnetUpdateTimeEntry(id: $id, input: $input) {\n id\n description\n durationMinutes\n date\n billingType\n updatedAt\n }\n }\n`;\n\nconst DELETE_TIME_ENTRY = gql`\n mutation TimeEntryControlsDelete($id: ID!) {\n planmagnetDeleteTimeEntry(id: $id)\n }\n`;\n\nconst FIELD =\n 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm disabled:opacity-60';\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\n\n/** The work item a \"Log time\" dialog logs against, when the caller already knows it (e.g. a task's own page). */\nexport interface TimeEntryLock {\n workItemId: string;\n projectId: string;\n /** Shown in the dialog instead of the project/work-item pickers, e.g. \"PM-42 — Ship the thing\". */\n label: string;\n}\n\n/**\n * Log time against a work item. With `lock`, the work item is fixed (the task-detail entry point); otherwise\n * the dialog offers a project picker and a work-item search, for logging time from the Time Entries page.\n */\nexport function LogTimeDialog({\n lock,\n refetchQueries,\n onLogged,\n triggerLabel = 'Log time',\n triggerSize = 'sm',\n}: {\n lock?: TimeEntryLock;\n /** Every query that must re-run after a successful log, e.g. ['TimeEntriesList'] or ['TaskTimeEntries']. */\n refetchQueries: string[];\n onLogged?: (id: string) => void;\n triggerLabel?: string;\n triggerSize?: 'sm' | 'default';\n}) {\n const [open, setOpen] = useState(false);\n const [projectId, setProjectId] = useState('');\n const [workItemId, setWorkItemId] = useState('');\n const [hours, setHours] = useState('');\n const [date, setDate] = useState(todayInputValue());\n const [billingType, setBillingType] = useState<BillingType>('NON_BILLABLE');\n const [description, setDescription] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [log, { loading }] = useMutation<{ planmagnetLogTime: { id: string } }>(LOG_TIME, {\n refetchQueries,\n awaitRefetchQueries: true,\n });\n\n const reset = () => {\n setProjectId('');\n setWorkItemId('');\n setHours('');\n setDate(todayInputValue());\n setBillingType('NON_BILLABLE');\n setDescription('');\n setError(null);\n };\n\n const effectiveWorkItemId = lock?.workItemId ?? workItemId;\n const effectiveProjectId = lock?.projectId ?? projectId;\n const minutes = hoursInputToMinutes(hours);\n const canSubmit =\n !loading && !!effectiveWorkItemId && !!effectiveProjectId && minutes > 0 && !!date;\n\n const submit = async () => {\n setError(null);\n try {\n const result = await throwOnError(\n log({\n variables: {\n input: {\n workItemId: effectiveWorkItemId,\n projectId: effectiveProjectId,\n durationMinutes: minutes,\n date,\n billingType,\n ...(description.trim() ? { description: description.trim() } : {}),\n },\n },\n })\n );\n const id = result.data?.planmagnetLogTime.id;\n setOpen(false);\n reset();\n if (id) onLogged?.(id);\n } catch (e) {\n setError(messageOf(e, 'The time could not be logged.'));\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={triggerSize} data-testid=\"log-time-open\">\n <Clock className=\"mr-1 h-4 w-4\" />\n {triggerLabel}\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Log time</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {lock\n ? `Against ${lock.label}.`\n : 'Pick a project and a work item, then the time spent.'}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n {!lock && (\n <>\n <div>\n <label htmlFor=\"log-time-project\" className=\"text-sm font-medium\">\n Project\n </label>\n <div className=\"mt-1\">\n <ProjectSelect\n id=\"log-time-project\"\n value={projectId}\n onChange={(next) => {\n setProjectId(next);\n setWorkItemId('');\n }}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"log-time-work-item\" className=\"text-sm font-medium\">\n Work item\n </label>\n <div className=\"mt-1\">\n <WorkItemSearchSelect\n id=\"log-time-work-item\"\n projectId={projectId || undefined}\n value={workItemId}\n onChange={setWorkItemId}\n />\n </div>\n </div>\n </>\n )}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"log-time-hours\" className=\"text-sm font-medium\">\n Hours\n </label>\n <Input\n id=\"log-time-hours\"\n className=\"mt-1\"\n type=\"number\"\n min=\"0\"\n step=\"0.25\"\n inputMode=\"decimal\"\n placeholder=\"e.g. 1.5\"\n value={hours}\n onChange={(e) => setHours(e.target.value)}\n />\n </div>\n <div>\n <label htmlFor=\"log-time-date\" className=\"text-sm font-medium\">\n Date\n </label>\n <Input\n id=\"log-time-date\"\n className=\"mt-1\"\n type=\"date\"\n value={date}\n onChange={(e) => setDate(e.target.value)}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"log-time-billing\" className=\"text-sm font-medium\">\n Billing type\n </label>\n <select\n id=\"log-time-billing\"\n className={FIELD}\n value={billingType}\n onChange={(e) => setBillingType(e.target.value as BillingType)}\n >\n {BILLING_TYPES.map((b) => (\n <option key={b} value={b}>\n {enumLabel(b)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"log-time-description\" className=\"text-sm font-medium\">\n Description\n </label>\n <textarea\n id=\"log-time-description\"\n className={`${FIELD} min-h-[64px]`}\n placeholder=\"What did you work on? (optional)\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button onClick={() => void submit()} disabled={!canSubmit} data-testid=\"log-time-submit\">\n {loading ? 'Logging…' : 'Log time'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport interface TimeEntryValues {\n id: string;\n description: string | null;\n durationMinutes: number;\n date: string;\n billingType: string;\n}\n\n/** Edit a time entry's description, duration, date and billing type. Only changed fields are sent. */\nexport function EditTimeEntryDialog({\n entry,\n refetchQueries,\n}: {\n entry: TimeEntryValues;\n refetchQueries: string[];\n}) {\n const initial = {\n description: entry.description ?? '',\n hours: minutesToHoursInput(entry.durationMinutes),\n date: toDateInputValue(entry.date),\n billingType: entry.billingType,\n };\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(initial);\n const [error, setError] = useState<string | null>(null);\n const [update, { loading }] = useMutation(UPDATE_TIME_ENTRY, {\n refetchQueries,\n awaitRefetchQueries: true,\n });\n\n const minutes = hoursInputToMinutes(form.hours);\n const canSubmit = !loading && minutes > 0 && !!form.date;\n\n const save = async () => {\n setError(null);\n const input: Record<string, unknown> = {};\n if (form.description.trim() !== initial.description.trim())\n input.description = form.description.trim() || null;\n if (minutes !== entry.durationMinutes) input.durationMinutes = minutes;\n if (form.date !== initial.date) input.date = form.date;\n if (form.billingType !== initial.billingType) input.billingType = form.billingType;\n if (Object.keys(input).length === 0) {\n setOpen(false);\n return;\n }\n try {\n await throwOnError(update({ variables: { id: entry.id, input } }));\n setOpen(false);\n } catch (e) {\n setError(messageOf(e, 'The time entry could not be saved.'));\n }\n };\n\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) setForm(initial);\n else setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"outline\"\n aria-label=\"Edit time entry\"\n data-testid=\"time-entry-edit\"\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Edit time entry</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Change the hours, date, billing type or description.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor=\"edit-time-hours\" className=\"text-sm font-medium\">\n Hours\n </label>\n <Input\n id=\"edit-time-hours\"\n className=\"mt-1\"\n type=\"number\"\n min=\"0\"\n step=\"0.25\"\n inputMode=\"decimal\"\n value={form.hours}\n onChange={(e) => setForm({ ...form, hours: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"edit-time-date\" className=\"text-sm font-medium\">\n Date\n </label>\n <Input\n id=\"edit-time-date\"\n className=\"mt-1\"\n type=\"date\"\n value={form.date}\n onChange={(e) => setForm({ ...form, date: e.target.value })}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"edit-time-billing\" className=\"text-sm font-medium\">\n Billing type\n </label>\n <select\n id=\"edit-time-billing\"\n className={FIELD}\n value={form.billingType}\n onChange={(e) => setForm({ ...form, billingType: e.target.value })}\n >\n {BILLING_TYPES.map((b) => (\n <option key={b} value={b}>\n {enumLabel(b)}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"edit-time-description\" className=\"text-sm font-medium\">\n Description\n </label>\n <textarea\n id=\"edit-time-description\"\n className={`${FIELD} min-h-[64px]`}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button onClick={() => void save()} disabled={!canSubmit} data-testid=\"edit-time-save\">\n {loading ? 'Saving…' : 'Save'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n/** Delete a time entry, with confirmation. */\nexport function DeleteTimeEntryButton({\n id,\n refetchQueries,\n}: {\n id: string;\n refetchQueries: string[];\n}) {\n const [remove, { loading }] = useMutation(DELETE_TIME_ENTRY, {\n refetchQueries,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id } }));\n } catch (e) {\n setError(messageOf(e, 'The time entry could not be deleted.'));\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <ConfirmButton\n label=\"\"\n ariaLabel=\"Delete time entry\"\n variant=\"outline\"\n icon={<Trash2 className=\"h-4 w-4\" />}\n title=\"Delete this time entry?\"\n description=\"The logged time is removed. This cannot be undone here.\"\n confirmLabel=\"Delete entry\"\n destructive\n disabled={loading}\n onConfirm={() => void onDelete()}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -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
|