@activecollab/components 2.0.365 → 2.0.366
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/cjs/presentation/listView/columns.js +967 -0
- package/dist/cjs/presentation/listView/columns.js.map +1 -0
- package/dist/esm/presentation/listView/columns.d.ts +53 -0
- package/dist/esm/presentation/listView/columns.d.ts.map +1 -0
- package/dist/esm/presentation/listView/columns.js +855 -0
- package/dist/esm/presentation/listView/columns.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
|
+
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
|
3
|
+
const _excluded = ["open", "className", "children"],
|
|
4
|
+
_excluded2 = ["labels"],
|
|
5
|
+
_excluded3 = ["value", "tooltip", "overdue"];
|
|
6
|
+
import React, { createContext, forwardRef, useCallback, useContext, useMemo, useState } from "react";
|
|
7
|
+
import moment from "moment-timezone";
|
|
8
|
+
import styled, { css } from "styled-components";
|
|
9
|
+
import { Button } from "../../components/Button";
|
|
10
|
+
import { Checkbox } from "../../components/Checkbox";
|
|
11
|
+
import { Chip } from "../../components/Chip";
|
|
12
|
+
import { DatePicker } from "../../components/DatePicker";
|
|
13
|
+
import { Dot } from "../../components/Dot";
|
|
14
|
+
import { CalendarIcon, ClockAddIcon, CollapseExpandSingleIcon } from "../../components/Icons";
|
|
15
|
+
import { InputHours } from "../../components/Input";
|
|
16
|
+
import { Label } from "../../components/Label";
|
|
17
|
+
import { Modal } from "../../components/Modal";
|
|
18
|
+
import { ProgressPie } from "../../components/ProgressPie";
|
|
19
|
+
import { Select } from "../../components/Select";
|
|
20
|
+
import { SelectTrigger } from "../../components/SelectTrigger";
|
|
21
|
+
import { Textarea } from "../../components/Textarea";
|
|
22
|
+
import { SlideFromTop } from "../../components/Transitions";
|
|
23
|
+
import { Body2, Caption1, Caption2, Header3 } from "../../components/Typography";
|
|
24
|
+
import { formatHours } from "../../utils";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Interactive list-view column cells for the `Presentation/List view` story —
|
|
28
|
+
* the Label, Date and Time cells with their editors, mirroring
|
|
29
|
+
* `new-activecollab-front/src/components/ListView/columns/*`:
|
|
30
|
+
*
|
|
31
|
+
* - **Label** (`columns/Label.js` + `LabelTrigger`/`LabelItems`): chips for 1–2
|
|
32
|
+
* labels, dots from 3 up, `+N` past 8, and a DS `Select` picker (search,
|
|
33
|
+
* colored dot + label name in the label's own colour, checkbox, "+ Add new").
|
|
34
|
+
* - **Date** (`columns/Date.js`): a range trigger that formats like the app
|
|
35
|
+
* (`formatDateRange`), turns red when overdue, and opens the DS `DatePicker`
|
|
36
|
+
* in atomic mode so it gets the Save / ✕ / Clear footer.
|
|
37
|
+
* - **Time** (`columns/Time.js`): `ProgressPie` + `tracked/estimated`, coloured
|
|
38
|
+
* by how much of the estimate is used, opening the "Add Time Record" dialog.
|
|
39
|
+
*
|
|
40
|
+
* Everything is local state — there is no store, so edits live as long as the
|
|
41
|
+
* story is mounted.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const SANS = '-apple-system, BlinkMacSystemFont, "Inter", "Roboto", "Helvetica Neue", Arial, sans-serif';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* CSS-only tooltip, mirroring the app's `.c-cooltip.c-cooltip--top` (see
|
|
48
|
+
* `styles/c_cooltip.less`). The list renders hundreds of these, and the DS
|
|
49
|
+
* `Tooltip` mounts a Portal + Popper per instance — far too heavy per cell — so
|
|
50
|
+
* the cells use `data-tooltip` exactly like the real list view does.
|
|
51
|
+
*/
|
|
52
|
+
const CooltipStyle = css(["&[data-tooltip],[data-tooltip]{position:relative;}&[data-tooltip]::after,[data-tooltip]::after{pointer-events:none;content:attr(data-tooltip);position:absolute;bottom:calc(100% + 0.5rem);left:50%;transform:scale(0.85) translateX(-50%);transform-origin:0;opacity:0;max-width:12.5rem;padding:0.25rem 0.625rem;border-radius:0.5rem;background-color:rgba(0,0,0,0.9);color:#fff;font-family:", ";font-size:0.75rem;font-weight:500;line-height:1.3;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}@media (hover:hover){&[data-tooltip]:not(:focus):hover::after,[data-tooltip]:not(:focus):hover::after{transition:opacity 0.15s ease,transform 0.15s ease;transition-delay:0.6s;transform:scale(1) translateX(-50%);opacity:1;}}"], SANS);
|
|
53
|
+
|
|
54
|
+
/* ================================================================== */
|
|
55
|
+
/* Shared trigger button (mirrors ListView/ListButton.js) */
|
|
56
|
+
/* ================================================================== */
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* `Menu` clones its `target` and injects `open`, so — like the app's
|
|
60
|
+
* `ListButton` — the trigger has to swallow that prop and turn it into the DS
|
|
61
|
+
* `active` state plus an `.open` class the row CSS uses to keep the trigger
|
|
62
|
+
* visible while its menu is open.
|
|
63
|
+
*/
|
|
64
|
+
const ListButtonBase = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
65
|
+
let open = _ref.open,
|
|
66
|
+
className = _ref.className,
|
|
67
|
+
children = _ref.children,
|
|
68
|
+
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
|
|
69
|
+
return /*#__PURE__*/React.createElement(Button, _extends({}, rest, {
|
|
70
|
+
ref: ref,
|
|
71
|
+
type: "button",
|
|
72
|
+
variant: "text gray",
|
|
73
|
+
active: open,
|
|
74
|
+
className: "list-button" + (className ? " " + className : "") + (open ? " open" : "")
|
|
75
|
+
}), children);
|
|
76
|
+
});
|
|
77
|
+
ListButtonBase.displayName = "ListButtonBase";
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* The DS Button treats a single element child as "icon only" — inline-flex,
|
|
81
|
+
* centered, a fixed 32px wide and `overflow: hidden`, which squashes a date or
|
|
82
|
+
* a chip. `&&` doubles the specificity so these rules win over that block; the
|
|
83
|
+
* button then hugs its content and the column's `justify-content: flex-end`
|
|
84
|
+
* parks it against the right edge.
|
|
85
|
+
*/
|
|
86
|
+
const StyledListButton = styled(ListButtonBase).withConfig({
|
|
87
|
+
displayName: "columns__StyledListButton",
|
|
88
|
+
componentId: "sc-9f4385-0"
|
|
89
|
+
})(["&&{display:inline-flex;align-items:center;justify-content:flex-end;width:auto;min-width:0;max-width:100%;padding:4px 8px;overflow:visible;text-align:right;}", ""], CooltipStyle);
|
|
90
|
+
|
|
91
|
+
/* ================================================================== */
|
|
92
|
+
/* Labels */
|
|
93
|
+
/* ================================================================== */
|
|
94
|
+
|
|
95
|
+
/* The project's label catalogue. Names are uppercase, like in the app. */
|
|
96
|
+
const INITIAL_LABELS = [{
|
|
97
|
+
id: "in-progress",
|
|
98
|
+
name: "IN PROGRESS",
|
|
99
|
+
color: "#CDE8F5",
|
|
100
|
+
textColor: "#3D7F9E"
|
|
101
|
+
}, {
|
|
102
|
+
id: "needs-review",
|
|
103
|
+
name: "NEEDS REVIEW",
|
|
104
|
+
color: "#F9E79F",
|
|
105
|
+
textColor: "#A98A17"
|
|
106
|
+
}, {
|
|
107
|
+
id: "approved",
|
|
108
|
+
name: "APPROVED",
|
|
109
|
+
color: "#A9DFBF",
|
|
110
|
+
textColor: "#3D9A6A"
|
|
111
|
+
}, {
|
|
112
|
+
id: "blocked",
|
|
113
|
+
name: "BLOCKED",
|
|
114
|
+
color: "#F1948A",
|
|
115
|
+
textColor: "#B5544C"
|
|
116
|
+
}, {
|
|
117
|
+
id: "on-hold",
|
|
118
|
+
name: "ON HOLD",
|
|
119
|
+
color: "#F5CBA7",
|
|
120
|
+
textColor: "#B4713A"
|
|
121
|
+
}, {
|
|
122
|
+
id: "idea",
|
|
123
|
+
name: "IDEA",
|
|
124
|
+
color: "#FAD7A0",
|
|
125
|
+
textColor: "#B98A2B"
|
|
126
|
+
}, {
|
|
127
|
+
id: "todo",
|
|
128
|
+
name: "TODO",
|
|
129
|
+
color: "#D6EAF8",
|
|
130
|
+
textColor: "#4A87B5"
|
|
131
|
+
}, {
|
|
132
|
+
id: "research",
|
|
133
|
+
name: "RESEARCH",
|
|
134
|
+
color: "#D7BDE2",
|
|
135
|
+
textColor: "#8155A0"
|
|
136
|
+
}, {
|
|
137
|
+
id: "accessibility",
|
|
138
|
+
name: "ACCESSIBILITY",
|
|
139
|
+
color: "#AED6F1",
|
|
140
|
+
textColor: "#3E7CA6"
|
|
141
|
+
}, {
|
|
142
|
+
id: "copy",
|
|
143
|
+
name: "COPY",
|
|
144
|
+
color: "#F5B7B1",
|
|
145
|
+
textColor: "#B5544C"
|
|
146
|
+
}, {
|
|
147
|
+
id: "design-debt",
|
|
148
|
+
name: "DESIGN DEBT",
|
|
149
|
+
color: "#E8DAEF",
|
|
150
|
+
textColor: "#7D5A99"
|
|
151
|
+
}, {
|
|
152
|
+
id: "qa",
|
|
153
|
+
name: "QA",
|
|
154
|
+
color: "#A3E4D7",
|
|
155
|
+
textColor: "#34907C"
|
|
156
|
+
}, {
|
|
157
|
+
id: "client-input",
|
|
158
|
+
name: "CLIENT INPUT",
|
|
159
|
+
color: "#FDEBD0",
|
|
160
|
+
textColor: "#B08640"
|
|
161
|
+
}, {
|
|
162
|
+
id: "mobile",
|
|
163
|
+
name: "MOBILE",
|
|
164
|
+
color: "#D1F2EB",
|
|
165
|
+
textColor: "#2E8B7A"
|
|
166
|
+
}, {
|
|
167
|
+
id: "performance",
|
|
168
|
+
name: "PERFORMANCE",
|
|
169
|
+
color: "#FCF3CF",
|
|
170
|
+
textColor: "#A38B1E"
|
|
171
|
+
}, {
|
|
172
|
+
id: "asset",
|
|
173
|
+
name: "ASSET",
|
|
174
|
+
color: "#D5D8DC",
|
|
175
|
+
textColor: "#6B7378"
|
|
176
|
+
}];
|
|
177
|
+
|
|
178
|
+
/* Colours handed out to labels created from the picker's "+ Add new" row. */
|
|
179
|
+
const NEW_LABEL_COLORS = [{
|
|
180
|
+
color: "#F9E0F2",
|
|
181
|
+
textColor: "#A4569B"
|
|
182
|
+
}, {
|
|
183
|
+
color: "#DDEBFB",
|
|
184
|
+
textColor: "#4E7BA8"
|
|
185
|
+
}, {
|
|
186
|
+
color: "#E4F6D8",
|
|
187
|
+
textColor: "#5F9440"
|
|
188
|
+
}, {
|
|
189
|
+
color: "#FDE2D5",
|
|
190
|
+
textColor: "#B36A45"
|
|
191
|
+
}];
|
|
192
|
+
const LabelsContext = /*#__PURE__*/createContext({
|
|
193
|
+
labels: INITIAL_LABELS,
|
|
194
|
+
labelsById: {},
|
|
195
|
+
addLabel: () => ""
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Holds the label catalogue for the whole list, so a label created in one
|
|
200
|
+
* task's picker shows up in every other picker too.
|
|
201
|
+
*/
|
|
202
|
+
export const LabelsProvider = _ref2 => {
|
|
203
|
+
let children = _ref2.children;
|
|
204
|
+
const _useState = useState(INITIAL_LABELS),
|
|
205
|
+
labels = _useState[0],
|
|
206
|
+
setLabels = _useState[1];
|
|
207
|
+
const addLabel = useCallback(name => {
|
|
208
|
+
const trimmed = name.trim().toUpperCase();
|
|
209
|
+
const id = "label-" + trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
210
|
+
setLabels(prev => {
|
|
211
|
+
if (prev.some(label => label.id === id)) {
|
|
212
|
+
return prev;
|
|
213
|
+
}
|
|
214
|
+
const palette = NEW_LABEL_COLORS[prev.length % NEW_LABEL_COLORS.length];
|
|
215
|
+
return [...prev, _extends({
|
|
216
|
+
id,
|
|
217
|
+
name: trimmed
|
|
218
|
+
}, palette)];
|
|
219
|
+
});
|
|
220
|
+
return id;
|
|
221
|
+
}, []);
|
|
222
|
+
const value = useMemo(() => ({
|
|
223
|
+
labels,
|
|
224
|
+
labelsById: labels.reduce((acc, label) => _extends({}, acc, {
|
|
225
|
+
[label.id]: label
|
|
226
|
+
}), {}),
|
|
227
|
+
addLabel
|
|
228
|
+
}), [addLabel, labels]);
|
|
229
|
+
return /*#__PURE__*/React.createElement(LabelsContext.Provider, {
|
|
230
|
+
value: value
|
|
231
|
+
}, children);
|
|
232
|
+
};
|
|
233
|
+
const useTaskLabels = () => useContext(LabelsContext);
|
|
234
|
+
|
|
235
|
+
/* Empty state pill (mirrors EntityLabel + .entity-label in list-view.less) */
|
|
236
|
+
const StyledLabelPlaceholder = styled.span.withConfig({
|
|
237
|
+
displayName: "columns__StyledLabelPlaceholder",
|
|
238
|
+
componentId: "sc-9f4385-1"
|
|
239
|
+
})(["display:inline-flex;align-items:center;padding:1px 6px;border:1px solid var(--color-theme-700);border-radius:4px;.list-button.open &,.list-button:hover &{border-color:var(--color-primary-700,var(--color-primary));}"]);
|
|
240
|
+
const StyledLabelItems = styled.span.withConfig({
|
|
241
|
+
displayName: "columns__StyledLabelItems",
|
|
242
|
+
componentId: "sc-9f4385-2"
|
|
243
|
+
})(["display:inline-flex;align-items:center;min-width:0;> *:not(:last-child){margin-right:2px;}.rest-count{margin-left:2px;}", ""], CooltipStyle);
|
|
244
|
+
const MAX_SHOWN_LABELS = 8;
|
|
245
|
+
const LabelItems = _ref3 => {
|
|
246
|
+
let labels = _ref3.labels;
|
|
247
|
+
const showAsDot = labels.length > 2;
|
|
248
|
+
let shown = labels;
|
|
249
|
+
let restCount = 0;
|
|
250
|
+
if (labels.length > MAX_SHOWN_LABELS) {
|
|
251
|
+
restCount = Math.min(labels.length - MAX_SHOWN_LABELS, 99);
|
|
252
|
+
shown = labels.slice(0, MAX_SHOWN_LABELS - 1);
|
|
253
|
+
}
|
|
254
|
+
return /*#__PURE__*/React.createElement(StyledLabelItems, null, shown.map(label => showAsDot ? /*#__PURE__*/React.createElement(Dot, {
|
|
255
|
+
key: label.id,
|
|
256
|
+
color: label.color,
|
|
257
|
+
size: 20,
|
|
258
|
+
"data-tooltip": label.name
|
|
259
|
+
}) : /*#__PURE__*/React.createElement(Chip, {
|
|
260
|
+
key: label.id,
|
|
261
|
+
label: label.name,
|
|
262
|
+
backgroundColor: label.color,
|
|
263
|
+
color: "#303037",
|
|
264
|
+
style: {
|
|
265
|
+
maxWidth: "90px"
|
|
266
|
+
},
|
|
267
|
+
typographyProps: {
|
|
268
|
+
variant: "Caption 2",
|
|
269
|
+
weight: "bold"
|
|
270
|
+
},
|
|
271
|
+
"data-tooltip": label.name
|
|
272
|
+
})), restCount > 0 ? /*#__PURE__*/React.createElement(Body2, {
|
|
273
|
+
weight: "bold",
|
|
274
|
+
className: "rest-count"
|
|
275
|
+
}, "+", restCount) : null);
|
|
276
|
+
};
|
|
277
|
+
const LabelTrigger = /*#__PURE__*/forwardRef((_ref4, ref) => {
|
|
278
|
+
let labels = _ref4.labels,
|
|
279
|
+
rest = _objectWithoutPropertiesLoose(_ref4, _excluded2);
|
|
280
|
+
if (labels.length === 0) {
|
|
281
|
+
return /*#__PURE__*/React.createElement(StyledListButton, _extends({}, rest, {
|
|
282
|
+
ref: ref,
|
|
283
|
+
className: "show-on-hover",
|
|
284
|
+
"aria-label": "Add labels",
|
|
285
|
+
"data-tooltip": "Add Labels"
|
|
286
|
+
}), /*#__PURE__*/React.createElement(StyledLabelPlaceholder, null, /*#__PURE__*/React.createElement(Caption2, {
|
|
287
|
+
weight: "bold",
|
|
288
|
+
color: "tertiary"
|
|
289
|
+
}, "LABEL")));
|
|
290
|
+
}
|
|
291
|
+
return /*#__PURE__*/React.createElement(StyledListButton, _extends({}, rest, {
|
|
292
|
+
ref: ref,
|
|
293
|
+
"aria-label": "Change labels"
|
|
294
|
+
}), /*#__PURE__*/React.createElement(LabelItems, {
|
|
295
|
+
labels: labels
|
|
296
|
+
}));
|
|
297
|
+
});
|
|
298
|
+
LabelTrigger.displayName = "LabelTrigger";
|
|
299
|
+
export const LabelCell = _ref5 => {
|
|
300
|
+
let labelIds = _ref5.labelIds,
|
|
301
|
+
_ref5$completed = _ref5.completed,
|
|
302
|
+
completed = _ref5$completed === void 0 ? false : _ref5$completed;
|
|
303
|
+
const _useTaskLabels = useTaskLabels(),
|
|
304
|
+
catalog = _useTaskLabels.labels,
|
|
305
|
+
labelsById = _useTaskLabels.labelsById,
|
|
306
|
+
addLabel = _useTaskLabels.addLabel;
|
|
307
|
+
const _useState2 = useState(labelIds != null ? labelIds : []),
|
|
308
|
+
selected = _useState2[0],
|
|
309
|
+
setSelected = _useState2[1];
|
|
310
|
+
|
|
311
|
+
/* alphabetical, like LabelTrigger sorts before rendering */
|
|
312
|
+
const selectedLabels = useMemo(() => selected.map(id => labelsById[id]).filter(label => Boolean(label)).sort((a, b) => a.name.localeCompare(b.name)), [labelsById, selected]);
|
|
313
|
+
const options = useMemo(() => catalog.map(label => ({
|
|
314
|
+
id: label.id,
|
|
315
|
+
name: label.name,
|
|
316
|
+
color: label.color,
|
|
317
|
+
textColor: label.textColor
|
|
318
|
+
})), [catalog]);
|
|
319
|
+
const handleChange = useCallback(value => {
|
|
320
|
+
setSelected(Array.isArray(value) ? value.map(String) : [String(value)].filter(Boolean));
|
|
321
|
+
}, []);
|
|
322
|
+
const handleAddNew = useCallback(name => {
|
|
323
|
+
if (!name || !name.trim()) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const id = addLabel(name);
|
|
327
|
+
setSelected(prev => prev.includes(id) ? prev : [...prev, id]);
|
|
328
|
+
}, [addLabel]);
|
|
329
|
+
|
|
330
|
+
/* Completed tasks show their labels read-only (mirrors Label.js) */
|
|
331
|
+
if (completed) {
|
|
332
|
+
return /*#__PURE__*/React.createElement(StyledReadOnlyCell, {
|
|
333
|
+
className: "col col-label"
|
|
334
|
+
}, selectedLabels.length > 0 ? /*#__PURE__*/React.createElement(LabelItems, {
|
|
335
|
+
labels: selectedLabels
|
|
336
|
+
}) : null);
|
|
337
|
+
}
|
|
338
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
339
|
+
className: "col col-label"
|
|
340
|
+
}, /*#__PURE__*/React.createElement(Select, {
|
|
341
|
+
type: "multiple",
|
|
342
|
+
options: options,
|
|
343
|
+
selected: selected,
|
|
344
|
+
onChange: handleChange,
|
|
345
|
+
emptyValue: "Add new",
|
|
346
|
+
emptyAction: handleAddNew,
|
|
347
|
+
autoHeightMax: 278,
|
|
348
|
+
position: "bottom-end",
|
|
349
|
+
target: /*#__PURE__*/React.createElement(LabelTrigger, {
|
|
350
|
+
labels: selectedLabels
|
|
351
|
+
})
|
|
352
|
+
}));
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/* ================================================================== */
|
|
356
|
+
/* Date */
|
|
357
|
+
/* ================================================================== */
|
|
358
|
+
|
|
359
|
+
/** The personalized date format the app defaults to (`MMM D. YYYY`). */
|
|
360
|
+
const DATE_FORMAT = "MMM D. YYYY";
|
|
361
|
+
|
|
362
|
+
/** Midnight UTC today — the reference point for the mock data and overdue. */
|
|
363
|
+
const todayUnix = () => moment.utc().startOf("day").unix();
|
|
364
|
+
|
|
365
|
+
/** Build a mock timestamp N days from today (negative = in the past). */
|
|
366
|
+
export const dayOffset = offset => moment.utc().startOf("day").add(offset, "days").unix();
|
|
367
|
+
|
|
368
|
+
/** mirrors utils/date.js → formatDateRange (month-first format) */
|
|
369
|
+
export const formatDateRange = (from, to) => {
|
|
370
|
+
if (from === undefined || from === null) {
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
const start = moment.unix(from).utc();
|
|
374
|
+
const due = moment.unix(to != null ? to : from).utc();
|
|
375
|
+
const withoutYear = DATE_FORMAT.replace(/YYYY|,$/gi, "").trim();
|
|
376
|
+
const withoutYearAndMonth = withoutYear.replace(/MMM|,$/gi, "").trim();
|
|
377
|
+
if (from === to || to === undefined) {
|
|
378
|
+
return start.format(withoutYear);
|
|
379
|
+
}
|
|
380
|
+
if (start.year() === due.year() && start.month() === due.month()) {
|
|
381
|
+
return start.format(withoutYear) + " - " + due.format(withoutYearAndMonth);
|
|
382
|
+
}
|
|
383
|
+
return start.format(withoutYear) + " - " + due.format(withoutYear);
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
/** mirrors utils/date.js → formatDateSmart(…, long) */
|
|
387
|
+
const formatDateLong = value => value ? moment.unix(value).utc().format(DATE_FORMAT) : "";
|
|
388
|
+
export const isOverdue = (dueOn, completed) => Boolean(dueOn && !completed && dueOn < todayUnix());
|
|
389
|
+
const StyledDateValue = styled.span.withConfig({
|
|
390
|
+
displayName: "columns__StyledDateValue",
|
|
391
|
+
componentId: "sc-9f4385-3"
|
|
392
|
+
})(["font-family:", ";white-space:nowrap;"], SANS);
|
|
393
|
+
|
|
394
|
+
/* completed rows render values without a trigger, so the cell itself hosts the
|
|
395
|
+
CSS tooltip */
|
|
396
|
+
const StyledReadOnlyCell = styled.div.withConfig({
|
|
397
|
+
displayName: "columns__StyledReadOnlyCell",
|
|
398
|
+
componentId: "sc-9f4385-4"
|
|
399
|
+
})(["padding-right:8px;", ""], CooltipStyle);
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* `DatePicker` wraps itself in a `width: 100%` inline-block, which would stretch
|
|
403
|
+
* across the column and left-align the trigger the moment the picker mounts —
|
|
404
|
+
* the value would visibly jump and the popper would anchor to the wrong spot.
|
|
405
|
+
* Keeping that wrapper right-aligned makes the open state geometrically
|
|
406
|
+
* identical to the closed one.
|
|
407
|
+
*/
|
|
408
|
+
const StyledPickerCell = styled.div.withConfig({
|
|
409
|
+
displayName: "columns__StyledPickerCell",
|
|
410
|
+
componentId: "sc-9f4385-5"
|
|
411
|
+
})(["> div{text-align:right;}"]);
|
|
412
|
+
const DateTrigger = /*#__PURE__*/forwardRef((_ref6, ref) => {
|
|
413
|
+
let value = _ref6.value,
|
|
414
|
+
tooltip = _ref6.tooltip,
|
|
415
|
+
overdue = _ref6.overdue,
|
|
416
|
+
rest = _objectWithoutPropertiesLoose(_ref6, _excluded3);
|
|
417
|
+
return /*#__PURE__*/React.createElement(StyledListButton, _extends({}, rest, {
|
|
418
|
+
ref: ref,
|
|
419
|
+
className: value ? "show-on-hover has-value" : "show-on-hover",
|
|
420
|
+
"aria-label": value ? "Change dates" : "Add date",
|
|
421
|
+
"data-tooltip": tooltip
|
|
422
|
+
}), value ? /*#__PURE__*/React.createElement(StyledDateValue, null, /*#__PURE__*/React.createElement(Caption1, {
|
|
423
|
+
weight: "bold",
|
|
424
|
+
color: overdue ? "alert" : "secondary"
|
|
425
|
+
}, value)) : /*#__PURE__*/React.createElement(CalendarIcon, null));
|
|
426
|
+
});
|
|
427
|
+
DateTrigger.displayName = "DateTrigger";
|
|
428
|
+
export const DateCell = _ref7 => {
|
|
429
|
+
var _dates$from, _dates$to2;
|
|
430
|
+
let startOn = _ref7.startOn,
|
|
431
|
+
dueOn = _ref7.dueOn,
|
|
432
|
+
_ref7$completed = _ref7.completed,
|
|
433
|
+
completed = _ref7$completed === void 0 ? false : _ref7$completed;
|
|
434
|
+
const _useState3 = useState({
|
|
435
|
+
from: startOn,
|
|
436
|
+
to: dueOn
|
|
437
|
+
}),
|
|
438
|
+
dates = _useState3[0],
|
|
439
|
+
setDates = _useState3[1];
|
|
440
|
+
/**
|
|
441
|
+
* The DS `DatePicker` does real work on mount (timezone guess, month grids,
|
|
442
|
+
* key handlers) — with one per row that is the single most expensive thing on
|
|
443
|
+
* this page. So the closed state is just the trigger; the picker is mounted
|
|
444
|
+
* on first click, already open, and unmounted again when it closes.
|
|
445
|
+
*/
|
|
446
|
+
const _useState4 = useState(false),
|
|
447
|
+
pickerMounted = _useState4[0],
|
|
448
|
+
setPickerMounted = _useState4[1];
|
|
449
|
+
|
|
450
|
+
/* moment isn't cheap either; only reformat when the dates actually change */
|
|
451
|
+
const _useMemo = useMemo(() => {
|
|
452
|
+
var _dates$to;
|
|
453
|
+
const formatted = formatDateRange(dates.from, dates.to);
|
|
454
|
+
let hint = "Add Date";
|
|
455
|
+
if (dates.from) {
|
|
456
|
+
hint = !dates.to || dates.to === dates.from ? formatDateLong(dates.from) : formatDateLong(dates.from) + " - " + formatDateLong(dates.to);
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
value: formatted,
|
|
460
|
+
overdue: isOverdue((_dates$to = dates.to) != null ? _dates$to : dates.from, completed),
|
|
461
|
+
tooltip: hint
|
|
462
|
+
};
|
|
463
|
+
}, [completed, dates.from, dates.to]),
|
|
464
|
+
value = _useMemo.value,
|
|
465
|
+
overdue = _useMemo.overdue,
|
|
466
|
+
tooltip = _useMemo.tooltip;
|
|
467
|
+
const handleSave = useCallback(range => {
|
|
468
|
+
if (!range || range.from === null || range.from === undefined) {
|
|
469
|
+
setDates({});
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
setDates({
|
|
473
|
+
from: Number(range.from),
|
|
474
|
+
to: range.to ? Number(range.to) : Number(range.from)
|
|
475
|
+
});
|
|
476
|
+
}, []);
|
|
477
|
+
const handleClear = useCallback(() => setDates({}), []);
|
|
478
|
+
const handleCalendarToggle = useCallback(next => {
|
|
479
|
+
if (!next) {
|
|
480
|
+
setPickerMounted(false);
|
|
481
|
+
}
|
|
482
|
+
}, []);
|
|
483
|
+
|
|
484
|
+
/* Completed tasks show the date read-only (mirrors Date.js) */
|
|
485
|
+
if (completed) {
|
|
486
|
+
return /*#__PURE__*/React.createElement(StyledReadOnlyCell, {
|
|
487
|
+
className: "col col-date"
|
|
488
|
+
}, value ? /*#__PURE__*/React.createElement(StyledDateValue, {
|
|
489
|
+
"data-tooltip": tooltip
|
|
490
|
+
}, /*#__PURE__*/React.createElement(Caption1, {
|
|
491
|
+
weight: "bold",
|
|
492
|
+
color: overdue ? "alert" : "secondary"
|
|
493
|
+
}, value)) : null);
|
|
494
|
+
}
|
|
495
|
+
if (!pickerMounted) {
|
|
496
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
497
|
+
className: "col col-date"
|
|
498
|
+
}, /*#__PURE__*/React.createElement(DateTrigger, {
|
|
499
|
+
value: value,
|
|
500
|
+
tooltip: tooltip,
|
|
501
|
+
overdue: overdue,
|
|
502
|
+
onClick: () => setPickerMounted(true)
|
|
503
|
+
}));
|
|
504
|
+
}
|
|
505
|
+
return /*#__PURE__*/React.createElement(StyledPickerCell, {
|
|
506
|
+
className: "col col-date"
|
|
507
|
+
}, /*#__PURE__*/React.createElement(DatePicker, {
|
|
508
|
+
open: true,
|
|
509
|
+
mode: "custom",
|
|
510
|
+
instant: false,
|
|
511
|
+
maxRange: 366,
|
|
512
|
+
firstDayOfWeek: 1,
|
|
513
|
+
position: "bottom-end",
|
|
514
|
+
selected: {
|
|
515
|
+
from: (_dates$from = dates.from) != null ? _dates$from : null,
|
|
516
|
+
to: (_dates$to2 = dates.to) != null ? _dates$to2 : null
|
|
517
|
+
},
|
|
518
|
+
onSave: handleSave,
|
|
519
|
+
onClear: handleClear,
|
|
520
|
+
onCalendarToggle: handleCalendarToggle,
|
|
521
|
+
target: /*#__PURE__*/React.createElement(DateTrigger, {
|
|
522
|
+
value: value,
|
|
523
|
+
tooltip: tooltip,
|
|
524
|
+
overdue: overdue
|
|
525
|
+
})
|
|
526
|
+
}));
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
/* ================================================================== */
|
|
530
|
+
/* Time */
|
|
531
|
+
/* ================================================================== */
|
|
532
|
+
|
|
533
|
+
/** mirrors utils/utils.jsx → calculateTimeToEstimatePercentage */
|
|
534
|
+
const timeToEstimatePercentage = (tracked, estimated) => {
|
|
535
|
+
if (estimated > 0) {
|
|
536
|
+
return tracked > 0 ? Math.round(tracked / estimated * 100) : 0;
|
|
537
|
+
}
|
|
538
|
+
return null;
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
/** mirrors utils/utils.jsx → formatTrackedAndEstimatedTime */
|
|
542
|
+
const formatTrackedAndEstimated = (tracked, estimated) => estimated > 0 ? formatHours(tracked, false, false, "short") + "/" + formatHours(estimated, false, false, "short") : formatHours(tracked, false, false, "short");
|
|
543
|
+
|
|
544
|
+
/* mirrors Time.js → progressColor() */
|
|
545
|
+
const progressColor = percent => {
|
|
546
|
+
if (percent >= 100) {
|
|
547
|
+
return "var(--red-alert)";
|
|
548
|
+
}
|
|
549
|
+
if (percent >= 75) {
|
|
550
|
+
return "var(--warning)";
|
|
551
|
+
}
|
|
552
|
+
return "var(--color-sucess-green)";
|
|
553
|
+
};
|
|
554
|
+
const StyledTimeValue = styled.span.withConfig({
|
|
555
|
+
displayName: "columns__StyledTimeValue",
|
|
556
|
+
componentId: "sc-9f4385-6"
|
|
557
|
+
})(["display:inline-flex;align-items:center;font-family:", ";white-space:nowrap;.c-progress-pie,svg{flex-shrink:0;}.time-pie{margin-right:8px;}"], SANS);
|
|
558
|
+
const TimeValue = _ref8 => {
|
|
559
|
+
let tracked = _ref8.tracked,
|
|
560
|
+
estimated = _ref8.estimated;
|
|
561
|
+
const percent = timeToEstimatePercentage(tracked, estimated);
|
|
562
|
+
return /*#__PURE__*/React.createElement(StyledTimeValue, null, estimated > 0 ? /*#__PURE__*/React.createElement(ProgressPie, {
|
|
563
|
+
className: "time-pie",
|
|
564
|
+
radius: 10,
|
|
565
|
+
progress: percent != null ? percent : 0,
|
|
566
|
+
progressColor: progressColor(percent != null ? percent : 0),
|
|
567
|
+
backgroundColor: "var(--color-theme-300)"
|
|
568
|
+
}) : null, /*#__PURE__*/React.createElement(Caption1, {
|
|
569
|
+
weight: "bold"
|
|
570
|
+
}, formatTrackedAndEstimated(tracked, estimated)));
|
|
571
|
+
};
|
|
572
|
+
const TimeTrigger = _ref9 => {
|
|
573
|
+
let tracked = _ref9.tracked,
|
|
574
|
+
estimated = _ref9.estimated,
|
|
575
|
+
onClick = _ref9.onClick;
|
|
576
|
+
const hasTime = tracked > 0 || estimated > 0;
|
|
577
|
+
return /*#__PURE__*/React.createElement(StyledListButton, {
|
|
578
|
+
onClick: onClick,
|
|
579
|
+
className: hasTime ? "show-on-hover open" : "show-on-hover",
|
|
580
|
+
"aria-label": hasTime ? "Edit time" : "Add time",
|
|
581
|
+
"data-tooltip": hasTime ? "Edit Time" : "Add Time"
|
|
582
|
+
}, hasTime ? /*#__PURE__*/React.createElement(TimeValue, {
|
|
583
|
+
tracked: tracked,
|
|
584
|
+
estimated: estimated
|
|
585
|
+
}) : /*#__PURE__*/React.createElement(ClockAddIcon, null));
|
|
586
|
+
};
|
|
587
|
+
export const TimeCell = _ref0 => {
|
|
588
|
+
let taskName = _ref0.taskName,
|
|
589
|
+
_ref0$tracked = _ref0.tracked,
|
|
590
|
+
tracked = _ref0$tracked === void 0 ? 0 : _ref0$tracked,
|
|
591
|
+
_ref0$estimated = _ref0.estimated,
|
|
592
|
+
estimated = _ref0$estimated === void 0 ? 0 : _ref0$estimated,
|
|
593
|
+
users = _ref0.users,
|
|
594
|
+
currentUserId = _ref0.currentUserId,
|
|
595
|
+
_ref0$completed = _ref0.completed,
|
|
596
|
+
completed = _ref0$completed === void 0 ? false : _ref0$completed;
|
|
597
|
+
const _useState5 = useState(false),
|
|
598
|
+
open = _useState5[0],
|
|
599
|
+
setOpen = _useState5[1];
|
|
600
|
+
const _useState6 = useState(() => {
|
|
601
|
+
var _users$find$name, _users$find;
|
|
602
|
+
return tracked > 0 ? [{
|
|
603
|
+
id: 1,
|
|
604
|
+
date: dayOffset(-3),
|
|
605
|
+
userId: currentUserId,
|
|
606
|
+
userName: (_users$find$name = (_users$find = users.find(user => user.id === currentUserId)) == null ? void 0 : _users$find.name) != null ? _users$find$name : "You",
|
|
607
|
+
jobType: "Design",
|
|
608
|
+
hours: tracked,
|
|
609
|
+
billable: true
|
|
610
|
+
}] : [];
|
|
611
|
+
}),
|
|
612
|
+
records = _useState6[0],
|
|
613
|
+
setRecords = _useState6[1];
|
|
614
|
+
const total = useMemo(() => records.reduce((sum, record) => sum + record.hours, 0), [records]);
|
|
615
|
+
const handleAdd = useCallback(record => {
|
|
616
|
+
setRecords(prev => [_extends({}, record, {
|
|
617
|
+
id: prev.length + 1
|
|
618
|
+
}), ...prev]);
|
|
619
|
+
setOpen(false);
|
|
620
|
+
}, []);
|
|
621
|
+
|
|
622
|
+
/* Completed tasks show the value without the "add" affordance (mirrors
|
|
623
|
+
Time.js passing hideIcon for completed tasks) */
|
|
624
|
+
if (completed) {
|
|
625
|
+
return /*#__PURE__*/React.createElement(StyledReadOnlyCell, {
|
|
626
|
+
className: "col col-time"
|
|
627
|
+
}, total > 0 || estimated > 0 ? /*#__PURE__*/React.createElement(TimeValue, {
|
|
628
|
+
tracked: total,
|
|
629
|
+
estimated: estimated
|
|
630
|
+
}) : null);
|
|
631
|
+
}
|
|
632
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
633
|
+
className: "col col-time"
|
|
634
|
+
}, /*#__PURE__*/React.createElement(TimeTrigger, {
|
|
635
|
+
tracked: total,
|
|
636
|
+
estimated: estimated,
|
|
637
|
+
onClick: () => setOpen(true)
|
|
638
|
+
}), open ? /*#__PURE__*/React.createElement(TimeRecordDialog, {
|
|
639
|
+
open: open,
|
|
640
|
+
taskName: taskName,
|
|
641
|
+
users: users,
|
|
642
|
+
currentUserId: currentUserId,
|
|
643
|
+
records: records,
|
|
644
|
+
total: total,
|
|
645
|
+
onSubmit: handleAdd,
|
|
646
|
+
onClose: () => setOpen(false)
|
|
647
|
+
}) : null);
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
/* ------------------------------------------------------------------ */
|
|
651
|
+
/* Add Time Record dialog (mirrors TimeRecordForm/*) */
|
|
652
|
+
/* ------------------------------------------------------------------ */
|
|
653
|
+
|
|
654
|
+
const JOB_TYPES = [{
|
|
655
|
+
id: "design",
|
|
656
|
+
name: "Design"
|
|
657
|
+
}, {
|
|
658
|
+
id: "development",
|
|
659
|
+
name: "Development"
|
|
660
|
+
}, {
|
|
661
|
+
id: "devops",
|
|
662
|
+
name: "Devops"
|
|
663
|
+
}, {
|
|
664
|
+
id: "consulting",
|
|
665
|
+
name: "Consulting"
|
|
666
|
+
}, {
|
|
667
|
+
id: "qa",
|
|
668
|
+
name: "QA"
|
|
669
|
+
}];
|
|
670
|
+
|
|
671
|
+
/* The dialog paper — the app builds this one by hand rather than with DS
|
|
672
|
+
`Dialog` because it is 770px wide (`.time_record_form_modal_wrapper`). */
|
|
673
|
+
const StyledTimeRecordPaper = styled.div.withConfig({
|
|
674
|
+
displayName: "columns__StyledTimeRecordPaper",
|
|
675
|
+
componentId: "sc-9f4385-7"
|
|
676
|
+
})(["position:relative;width:770px;max-width:95vw;max-height:calc(100vh - 156px);margin:126px auto 30px auto;padding:20px 30px;overflow:auto;border-radius:8px;background-color:var(--page-paper-main);color:var(--color-theme-900);box-shadow:var(--shadow-primary);box-sizing:border-box;font-family:", ";*{box-sizing:border-box;}@media (max-width:830px){width:calc(100vw - 60px);}.trd-header{display:flex;align-items:center;justify-content:space-between;}.trd-title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;margin-right:16px;font-size:1rem;font-weight:700;}.trd-date button{width:auto;}.trd-date > div{text-align:right;}.trd-divider{margin:20px 0 32px 0;border-bottom:1px solid var(--border-primary);height:1px;}.trd-body{display:flex;justify-content:space-between;position:relative;}.trd-form{width:100%;}.trd-fields{display:flex;align-items:flex-end;}.trd-hours{width:144px;margin-right:16px;flex-shrink:0;}.trd-hours .c-input-wrapper,.trd-hours input{width:100%;}.trd-field{margin-right:16px;}.trd-field:last-of-type{margin-right:20px;}.trd-field .c-label{display:block;margin-bottom:2px;}.trd-field button{width:144px;}.trd-billable{display:flex;align-items:center;margin-top:24px;}.trd-billable .c-label{margin-left:8px;cursor:pointer;}.trd-description-trigger{display:inline-flex;align-items:center;margin-top:16px;color:var(--color-secondary);font-size:14px;font-weight:500;background:none;border:none;padding:0;cursor:pointer;font-family:", ";}.trd-description-trigger svg{margin-right:4px;fill:var(--color-primary);transition:transform 200ms ease;}.trd-description-trigger.collapsed svg{transform:rotate(180deg);}.trd-textarea{width:360px;max-width:100%;margin-top:8px;font-size:12px;resize:none;}.trd-actions{display:flex;margin-top:24px;}.trd-actions .c-button:first-child{margin-right:12px;}.trd-total{position:absolute;right:0;bottom:0;display:inline-flex;align-items:center;align-self:flex-end;background:none;border:none;padding:0;cursor:pointer;}.trd-total svg{margin-right:4px;fill:var(--color-primary);transition:transform 200ms ease;}.trd-total.collapsed svg{transform:rotate(180deg);}.trd-records{margin-top:32px;border-top:1px solid var(--border-primary);}.trd-record{display:flex;align-items:center;justify-content:space-between;height:36px;border-bottom:1px solid var(--color-theme-300);}.trd-record-left{display:flex;align-items:center;gap:12px;min-width:0;}"], SANS, SANS);
|
|
677
|
+
const TimeRecordDialog = _ref1 => {
|
|
678
|
+
let open = _ref1.open,
|
|
679
|
+
taskName = _ref1.taskName,
|
|
680
|
+
users = _ref1.users,
|
|
681
|
+
currentUserId = _ref1.currentUserId,
|
|
682
|
+
records = _ref1.records,
|
|
683
|
+
total = _ref1.total,
|
|
684
|
+
onSubmit = _ref1.onSubmit,
|
|
685
|
+
onClose = _ref1.onClose;
|
|
686
|
+
const _useState7 = useState(""),
|
|
687
|
+
value = _useState7[0],
|
|
688
|
+
setValue = _useState7[1];
|
|
689
|
+
const _useState8 = useState(todayUnix()),
|
|
690
|
+
date = _useState8[0],
|
|
691
|
+
setDate = _useState8[1];
|
|
692
|
+
const _useState9 = useState(currentUserId),
|
|
693
|
+
userId = _useState9[0],
|
|
694
|
+
setUserId = _useState9[1];
|
|
695
|
+
const _useState0 = useState("devops"),
|
|
696
|
+
jobTypeId = _useState0[0],
|
|
697
|
+
setJobTypeId = _useState0[1];
|
|
698
|
+
const _useState1 = useState(false),
|
|
699
|
+
billable = _useState1[0],
|
|
700
|
+
setBillable = _useState1[1];
|
|
701
|
+
const _useState10 = useState(true),
|
|
702
|
+
showDescription = _useState10[0],
|
|
703
|
+
setShowDescription = _useState10[1];
|
|
704
|
+
const _useState11 = useState(""),
|
|
705
|
+
description = _useState11[0],
|
|
706
|
+
setDescription = _useState11[1];
|
|
707
|
+
const _useState12 = useState(false),
|
|
708
|
+
showRecords = _useState12[0],
|
|
709
|
+
setShowRecords = _useState12[1];
|
|
710
|
+
const user = users.find(item => item.id === userId);
|
|
711
|
+
const jobType = JOB_TYPES.find(item => item.id === jobTypeId);
|
|
712
|
+
|
|
713
|
+
/** "1:30" / "1.5" → decimal hours */
|
|
714
|
+
const hours = useMemo(() => {
|
|
715
|
+
const trimmed = value.trim();
|
|
716
|
+
if (!trimmed) {
|
|
717
|
+
return 0;
|
|
718
|
+
}
|
|
719
|
+
if (trimmed.includes(":")) {
|
|
720
|
+
const _trimmed$split = trimmed.split(":"),
|
|
721
|
+
h = _trimmed$split[0],
|
|
722
|
+
m = _trimmed$split[1];
|
|
723
|
+
return Number(h || 0) + Number(m || 0) / 60;
|
|
724
|
+
}
|
|
725
|
+
return Number(trimmed) || 0;
|
|
726
|
+
}, [value]);
|
|
727
|
+
const handleSubmit = useCallback(() => {
|
|
728
|
+
var _user$name, _jobType$name;
|
|
729
|
+
onSubmit({
|
|
730
|
+
date,
|
|
731
|
+
userId,
|
|
732
|
+
userName: (_user$name = user == null ? void 0 : user.name) != null ? _user$name : "",
|
|
733
|
+
jobType: (_jobType$name = jobType == null ? void 0 : jobType.name) != null ? _jobType$name : "",
|
|
734
|
+
hours,
|
|
735
|
+
billable,
|
|
736
|
+
description
|
|
737
|
+
});
|
|
738
|
+
}, [billable, date, description, hours, jobType, onSubmit, user, userId]);
|
|
739
|
+
return /*#__PURE__*/React.createElement(Modal, {
|
|
740
|
+
open: open,
|
|
741
|
+
onClose: onClose
|
|
742
|
+
}, /*#__PURE__*/React.createElement(SlideFromTop, {
|
|
743
|
+
in: open
|
|
744
|
+
}, /*#__PURE__*/React.createElement(StyledTimeRecordPaper, {
|
|
745
|
+
role: "dialog",
|
|
746
|
+
"aria-label": "Add time record"
|
|
747
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
748
|
+
className: "trd-header"
|
|
749
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
750
|
+
className: "trd-title"
|
|
751
|
+
}, taskName), /*#__PURE__*/React.createElement("span", {
|
|
752
|
+
className: "trd-date"
|
|
753
|
+
}, /*#__PURE__*/React.createElement(DatePicker, {
|
|
754
|
+
mode: "daily",
|
|
755
|
+
required: true,
|
|
756
|
+
firstDayOfWeek: 1,
|
|
757
|
+
position: "bottom-end",
|
|
758
|
+
selected: {
|
|
759
|
+
from: date,
|
|
760
|
+
to: date
|
|
761
|
+
},
|
|
762
|
+
onSave: range => range != null && range.from ? setDate(Number(range.from)) : undefined,
|
|
763
|
+
target: /*#__PURE__*/React.createElement(SelectTrigger, null, formatDateRange(date, date))
|
|
764
|
+
}))), /*#__PURE__*/React.createElement("div", {
|
|
765
|
+
className: "trd-divider"
|
|
766
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
767
|
+
className: "trd-body"
|
|
768
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
769
|
+
className: "trd-form"
|
|
770
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
771
|
+
className: "trd-fields"
|
|
772
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
773
|
+
className: "trd-hours"
|
|
774
|
+
}, /*#__PURE__*/React.createElement(InputHours, {
|
|
775
|
+
autoFocus: true,
|
|
776
|
+
allowEmptyValue: true,
|
|
777
|
+
size: "biggest",
|
|
778
|
+
placeholder: "00:00",
|
|
779
|
+
value: value,
|
|
780
|
+
onChange: setValue
|
|
781
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
782
|
+
className: "trd-field"
|
|
783
|
+
}, /*#__PURE__*/React.createElement(Label, {
|
|
784
|
+
size: "small"
|
|
785
|
+
}, "User"), /*#__PURE__*/React.createElement(Select, {
|
|
786
|
+
options: users,
|
|
787
|
+
selected: userId,
|
|
788
|
+
forceCloseMenu: true,
|
|
789
|
+
onChange: next => setUserId(String(next)),
|
|
790
|
+
target: /*#__PURE__*/React.createElement(SelectTrigger, {
|
|
791
|
+
id: "trd-user"
|
|
792
|
+
}, user == null ? void 0 : user.name)
|
|
793
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
794
|
+
className: "trd-field"
|
|
795
|
+
}, /*#__PURE__*/React.createElement(Label, {
|
|
796
|
+
size: "small"
|
|
797
|
+
}, "Job Type"), /*#__PURE__*/React.createElement(Select, {
|
|
798
|
+
options: JOB_TYPES,
|
|
799
|
+
selected: jobTypeId,
|
|
800
|
+
forceCloseMenu: true,
|
|
801
|
+
onChange: next => setJobTypeId(String(next)),
|
|
802
|
+
target: /*#__PURE__*/React.createElement(SelectTrigger, {
|
|
803
|
+
id: "trd-job-type"
|
|
804
|
+
}, jobType == null ? void 0 : jobType.name)
|
|
805
|
+
})), /*#__PURE__*/React.createElement("span", {
|
|
806
|
+
className: "trd-billable"
|
|
807
|
+
}, /*#__PURE__*/React.createElement(Checkbox, {
|
|
808
|
+
id: "trd-billable",
|
|
809
|
+
checked: billable,
|
|
810
|
+
onChange: () => setBillable(prev => !prev)
|
|
811
|
+
}), /*#__PURE__*/React.createElement(Label, {
|
|
812
|
+
htmlFor: "trd-billable",
|
|
813
|
+
weight: "regular"
|
|
814
|
+
}, "Billable"))), /*#__PURE__*/React.createElement("button", {
|
|
815
|
+
type: "button",
|
|
816
|
+
className: showDescription ? "trd-description-trigger" : "trd-description-trigger collapsed",
|
|
817
|
+
onClick: () => setShowDescription(prev => !prev)
|
|
818
|
+
}, /*#__PURE__*/React.createElement(CollapseExpandSingleIcon, null), /*#__PURE__*/React.createElement("span", null, "Description")), showDescription ? /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Textarea, {
|
|
819
|
+
className: "trd-textarea",
|
|
820
|
+
value: description,
|
|
821
|
+
onChange: event => setDescription(event.target.value)
|
|
822
|
+
})) : null, /*#__PURE__*/React.createElement("div", {
|
|
823
|
+
className: "trd-actions"
|
|
824
|
+
}, /*#__PURE__*/React.createElement(Button, {
|
|
825
|
+
variant: "primary",
|
|
826
|
+
disabled: hours <= 0,
|
|
827
|
+
onClick: handleSubmit
|
|
828
|
+
}, "Add Time Record"), /*#__PURE__*/React.createElement(Button, {
|
|
829
|
+
variant: "secondary",
|
|
830
|
+
onClick: onClose
|
|
831
|
+
}, "Cancel"))), total > 0 ? /*#__PURE__*/React.createElement("button", {
|
|
832
|
+
type: "button",
|
|
833
|
+
className: showRecords ? "trd-total" : "trd-total collapsed",
|
|
834
|
+
onClick: () => setShowRecords(prev => !prev)
|
|
835
|
+
}, /*#__PURE__*/React.createElement(CollapseExpandSingleIcon, null), /*#__PURE__*/React.createElement(Header3, {
|
|
836
|
+
color: "theme-secondary",
|
|
837
|
+
tabularNums: true
|
|
838
|
+
}, "Total: ", formatHours(total, false, true), "h")) : null), showRecords && records.length > 0 ? /*#__PURE__*/React.createElement("div", {
|
|
839
|
+
className: "trd-records"
|
|
840
|
+
}, records.map(record => /*#__PURE__*/React.createElement("div", {
|
|
841
|
+
key: record.id,
|
|
842
|
+
className: "trd-record"
|
|
843
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
844
|
+
className: "trd-record-left"
|
|
845
|
+
}, /*#__PURE__*/React.createElement(Caption1, {
|
|
846
|
+
weight: "bold",
|
|
847
|
+
color: "secondary"
|
|
848
|
+
}, formatDateRange(record.date, record.date)), /*#__PURE__*/React.createElement(Body2, null, record.userName), /*#__PURE__*/React.createElement(Caption1, {
|
|
849
|
+
color: "secondary"
|
|
850
|
+
}, record.jobType)), /*#__PURE__*/React.createElement(Body2, {
|
|
851
|
+
weight: "bold",
|
|
852
|
+
tabularNums: true
|
|
853
|
+
}, formatHours(record.hours), "h")))) : null)));
|
|
854
|
+
};
|
|
855
|
+
//# sourceMappingURL=columns.js.map
|