• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

caleb531 / workday-time-calculator / 10481054446

21 Aug 2024 12:06AM UTC coverage: 92.342% (-0.2%) from 92.527%
10481054446

push

github

caleb531
Convert components to JSX

Also add Prettier and clsx.

367 of 399 branches covered (91.98%)

Branch coverage included in aggregate %.

519 of 544 new or added lines in 20 files covered. (95.4%)

1 existing line in 1 file now uncovered.

1936 of 2095 relevant lines covered (92.41%)

441.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.52
/scripts/components/summary.jsx
1
import clsx from 'clsx';
3✔
2
import m from 'mithril';
3✔
3
import copySvgUrl from '../../icons/copy.svg';
3✔
4
import doneSvgUrl from '../../icons/done.svg';
3✔
5

6
class SummaryComponent {
3✔
7
  // Store a reference to the current log, and make sure it's always up-to-date
8
  oninit({ attrs: { preferences, log } }) {
3✔
9
    this.preferences = preferences;
172✔
10
    this.log = log;
172✔
11
    this.setTimeFormat();
172✔
12
    // Ensure that time calculations are re-run when time system changes (e.g.
13
    // beyween 12-hour and 24-hour)
14
    preferences.on('change:timeSystem', () => {
172✔
15
      this.log.regenerate();
×
16
    });
172✔
17
    preferences.on('change:categorySortOrder', () => {
172✔
18
      this.log.regenerate();
×
19
    });
172✔
20
  }
172✔
21
  onupdate({ attrs: { log } }) {
3✔
22
    this.log = log;
389✔
23
    this.setTimeFormat();
389✔
24
  }
389✔
25

26
  // Set the format of displayed times based on the user's preferred time system
27
  // (e.g. 12-hour or 24-hour)
28
  setTimeFormat() {
3✔
29
    if (this.preferences?.timeSystem === '24-hour') {
561✔
30
      this.timeFormat = 'H:mm';
3✔
31
    } else {
561✔
32
      this.timeFormat = 'h:mm';
558✔
33
    }
558✔
34
  }
561✔
35

36
  // Pad the given time value with zeroes if necessary
37
  padWithZeroes(time) {
3✔
38
    if (Number(time) < 10) {
239✔
39
      return '0' + time;
54✔
40
    } else {
239✔
41
      return time;
185✔
42
    }
185✔
43
  }
239✔
44

45
  getFormattedDuration(duration) {
3✔
46
    let isNegative = duration.asMinutes() < 0;
239✔
47
    let hours = Math.abs(duration.hours());
239✔
48
    let minutes = this.padWithZeroes(Math.abs(duration.minutes()));
239✔
49
    return `${isNegative ? '-' : ''}${hours}:${minutes}`;
239!
50
  }
239✔
51

52
  async copyDescriptionToClipboard(copyButton) {
3✔
53
    const targetElement = document.querySelector(
×
54
      copyButton.getAttribute('data-clipboard-target')
×
55
    );
×
56
    await navigator.clipboard.writeText(targetElement.innerText);
×
57
    // Briefly indicate that the copy was successful
58
    const categoryIndex = copyButton.getAttribute('data-category-index');
×
59
    const category = this.log.categories[categoryIndex];
×
60
    category.copiedToClipboard = true;
×
61
    m.redraw();
×
62
    setTimeout(() => {
×
63
      category.copiedToClipboard = false;
×
64
      m.redraw();
×
65
    }, this.clipboardCopySuccessDelay);
×
66
  }
×
67

68
  getFormattedDescription(description) {
3✔
69
    return `- ${description}`;
173✔
70
  }
173✔
71

72
  view({ attrs: { log } }) {
3✔
73
    return log && log.categories.length > 0 ? (
561✔
74
      <div className="log-summary" data-testid="log-summary">
96✔
75
        <div className="log-summary-overview">
96✔
76
          <div className="log-summary-overview-main">
96✔
77
            <div className="log-total">
96✔
78
              <div className="log-total-time-name log-label">Total:</div>{' '}
96✔
79
              <div className="log-total-time log-value">
96✔
80
                {this.getFormattedDuration(log.totalDuration)}
96✔
81
              </div>
96✔
82
            </div>
96✔
83

84
            <div className="log-stats">
96✔
85
              {log.errors && log.errors.length > 0 ? (
96✔
86
                <div className="log-errors" data-testid="log-errors">
6✔
87
                  <span className="log-label">Errors:</span>{' '}
6✔
88
                  <div className="log-times log-error-times">
6✔
89
                    {log.errors.map((error) => {
6✔
90
                      return (
6✔
91
                        <div className="log-error">
6✔
92
                          <span className="log-error-start-time log-value">
6✔
93
                            {error.startTime.isValid()
6✔
94
                              ? error.startTime.format(this.timeFormat)
6!
NEW
95
                              : '?'}
×
96
                          </span>{' '}
6✔
97
                          <span className="log-error-separator log-separator">
6✔
98
                            to
99
                          </span>{' '}
6✔
100
                          <span className="log-error-end-time log-value">
6✔
101
                            {error.endTime.isValid()
6✔
102
                              ? error.endTime.format(this.timeFormat)
6!
NEW
103
                              : '?'}
×
104
                          </span>
6✔
105
                        </div>
6✔
106
                      );
107
                    })}
6✔
108
                  </div>
6✔
109
                </div>
6✔
110
              ) : null}
90✔
111

112
              {log.gaps && log.gaps.length > 0 ? (
96✔
113
                <div className="log-gaps" data-testid="log-gaps">
31✔
114
                  <span className="log-label">Gaps:</span>{' '}
31✔
115
                  <div className="log-times log-gap-times">
31✔
116
                    {log.gaps.map((gap) => {
31✔
117
                      return (
44✔
118
                        <div className="log-gap">
44✔
119
                          <span className="log-gap-start-time log-value">
44✔
120
                            {gap.startTime.isValid()
44✔
121
                              ? gap.startTime.format(this.timeFormat)
44!
NEW
122
                              : '?'}
×
123
                          </span>{' '}
44✔
124
                          <span className="log-gap-separator log-separator">
44✔
125
                            to
126
                          </span>{' '}
44✔
127
                          <span className="log-gap-end-time log-value">
44✔
128
                            {gap.endTime.isValid()
44✔
129
                              ? gap.endTime.format(this.timeFormat)
44!
NEW
130
                              : '?'}
×
131
                          </span>
44✔
132
                        </div>
44✔
133
                      );
134
                    })}
31✔
135
                  </div>
31✔
136
                </div>
31✔
137
              ) : null}
65✔
138

139
              {log.overlaps && log.overlaps.length > 0 ? (
96✔
140
                <div className="log-overlaps">
12✔
141
                  <span className="log-label">Overlaps:</span>{' '}
12✔
142
                  <div
12✔
143
                    className="log-times log-overlap-times"
12✔
144
                    data-testid="log-overlap-times"
12✔
145
                  >
146
                    {log.overlaps.map((overlap) => {
12✔
147
                      return (
15✔
148
                        <div className="log-overlap">
15✔
149
                          <span className="log-overlap-start-time log-value">
15✔
150
                            {overlap.startTime.isValid()
15✔
151
                              ? overlap.startTime.format(this.timeFormat)
15!
NEW
152
                              : '?'}
×
153
                          </span>{' '}
15✔
154
                          <span className="log-overlap-separator log-separator">
15✔
155
                            to
156
                          </span>{' '}
15✔
157
                          <span className="log-overlap-end-time log-value">
15✔
158
                            {overlap.endTime.isValid()
15✔
159
                              ? overlap.endTime.format(this.timeFormat)
15!
NEW
160
                              : '?'}
×
161
                          </span>{' '}
15✔
162
                          <span className="log-value-categories">
15✔
163
                            (
164
                            {overlap.categories
15✔
165
                              .map((category) => category.name)
15✔
166
                              .join(', ')}
15✔
167
                            )
168
                          </span>
15✔
169
                        </div>
15✔
170
                      );
171
                    })}
12✔
172
                  </div>
12✔
173
                </div>
12✔
174
              ) : null}
84✔
175
            </div>
96✔
176
          </div>
96✔
177

178
          {log.latestRange ? (
96✔
179
            <div className="log-latest-time">
96✔
180
              <span className="log-label">Latest Time:</span>{' '}
96✔
181
              <span className="log-latest-time-time">
96✔
182
                <span className="log-value">
96✔
183
                  {log.latestRange.endTime.format(this.timeFormat)}
96✔
184
                </span>{' '}
96✔
185
                <span className="log-value-category">
96✔
186
                  ({log.latestRange.category.name})
96✔
187
                </span>
96✔
188
              </span>
96✔
189
            </div>
96!
NEW
190
          ) : null}
×
191
        </div>
96✔
192

193
        <div className="log-summary-details">
96✔
194
          {log.categories.map((category, c) => {
96✔
195
            return (
149✔
196
              <div className="log-category">
149✔
197
                {category.totalDuration.asMinutes() > 0
149✔
198
                  ? [
143✔
199
                      <div className="log-category-header">
143✔
200
                        <span className="log-category-name log-label">
143✔
201
                          {category.name}:
143✔
202
                        </span>{' '}
143✔
203
                        <span className="log-category-total-time log-value">
143✔
204
                          {this.getFormattedDuration(category.totalDuration)}
143✔
205
                        </span>{' '}
143✔
206
                      </div>,
143✔
207

208
                      <div
143✔
209
                        className={clsx('log-category-descriptions-container', {
143✔
210
                          'copied-to-clipboard': category.copiedToClipboard
143✔
211
                        })}
143✔
212
                      >
213
                        {category.descriptions.length
143✔
214
                          ? [
85✔
215
                              <div
85✔
216
                                className="log-category-descriptions-copy-button"
85✔
217
                                data-clipboard-target={`#log-category-description-list-${c}`}
85✔
218
                                data-category-index={c}
85✔
219
                                onclick={(event) => {
85✔
NEW
220
                                  this.copyDescriptionToClipboard(
×
NEW
221
                                    event.currentTarget
×
NEW
222
                                  );
×
NEW
223
                                }}
×
224
                                title="Copy to Clipboard"
85✔
225
                              >
226
                                <img
85✔
227
                                  className="log-category-descriptions-copy-button-icon"
85✔
228
                                  src={
85✔
229
                                    category.copiedToClipboard
85!
NEW
230
                                      ? doneSvgUrl
×
231
                                      : copySvgUrl
85✔
232
                                  }
233
                                  alt="Copy to Clipboard"
85✔
234
                                />
85✔
235
                              </div>,
85✔
236
                              <ul
85✔
237
                                className={`log-category-descriptions-list`}
85✔
238
                                id={`log-category-description-list-${c}`}
85✔
239
                              >
240
                                {category.descriptions.map((description) => {
85✔
241
                                  return (
173✔
242
                                    <li className="log-category-description">
173✔
243
                                      {this.getFormattedDescription(
173✔
244
                                        description
173✔
245
                                      )}
173✔
246
                                    </li>
173✔
247
                                  );
248
                                })}
85✔
249
                              </ul>
85✔
250
                            ]
85✔
251
                          : null}
58✔
252
                      </div>
143✔
253
                    ]
143✔
254
                  : null}
6✔
255
              </div>
149✔
256
            );
257
          })}
96✔
258
        </div>
96✔
259
      </div>
96✔
260
    ) : null;
465✔
261
  }
561✔
262
}
3✔
263
// The number of milliseconds to display a 'done' checkmark after a category's
264
// description block has been successfully copied to the clipboard
265
SummaryComponent.prototype.clipboardCopySuccessDelay = 1500;
3✔
266

267
export default SummaryComponent;
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc