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

caleb531 / workday-time-calculator / 10481076447

21 Aug 2024 12:09AM UTC coverage: 92.336% (-0.2%) from 92.527%
10481076447

push

github

caleb531
Convert components to JSX

Also add Prettier and clsx.

354 of 385 branches covered (91.95%)

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.

1935 of 2094 relevant lines covered (92.41%)

441.91 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;
171✔
10
    this.log = log;
171✔
11
    this.setTimeFormat();
171✔
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', () => {
171✔
15
      this.log.regenerate();
×
16
    });
171✔
17
    preferences.on('change:categorySortOrder', () => {
171✔
18
      this.log.regenerate();
×
19
    });
171✔
20
  }
171✔
21
  onupdate({ attrs: { log } }) {
3✔
22
    this.log = log;
391✔
23
    this.setTimeFormat();
391✔
24
  }
391✔
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') {
562✔
30
      this.timeFormat = 'H:mm';
3✔
31
    } else {
562✔
32
      this.timeFormat = 'h:mm';
559✔
33
    }
559✔
34
  }
562✔
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}`;
183✔
70
  }
183✔
71

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

84
            <div className="log-stats">
95✔
85
              {log.errors && log.errors.length > 0 ? (
95✔
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}
89✔
111

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

139
              {log.overlaps && log.overlaps.length > 0 ? (
95✔
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}
83✔
175
            </div>
95✔
176
          </div>
95✔
177

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

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

208
                      <div
144✔
209
                        className={clsx('log-category-descriptions-container', {
144✔
210
                          'copied-to-clipboard': category.copiedToClipboard
144✔
211
                        })}
144✔
212
                      >
213
                        {category.descriptions.length
144✔
214
                          ? [
87✔
215
                              <div
87✔
216
                                className="log-category-descriptions-copy-button"
87✔
217
                                data-clipboard-target={`#log-category-description-list-${c}`}
87✔
218
                                data-category-index={c}
87✔
219
                                onclick={(event) => {
87✔
NEW
220
                                  this.copyDescriptionToClipboard(
×
NEW
221
                                    event.currentTarget
×
NEW
222
                                  );
×
NEW
223
                                }}
×
224
                                title="Copy to Clipboard"
87✔
225
                              >
226
                                <img
87✔
227
                                  className="log-category-descriptions-copy-button-icon"
87✔
228
                                  src={
87✔
229
                                    category.copiedToClipboard
87!
NEW
230
                                      ? doneSvgUrl
×
231
                                      : copySvgUrl
87✔
232
                                  }
233
                                  alt="Copy to Clipboard"
87✔
234
                                />
87✔
235
                              </div>,
87✔
236
                              <ul
87✔
237
                                className={`log-category-descriptions-list`}
87✔
238
                                id={`log-category-description-list-${c}`}
87✔
239
                              >
240
                                {category.descriptions.map((description) => {
87✔
241
                                  return (
183✔
242
                                    <li className="log-category-description">
183✔
243
                                      {this.getFormattedDescription(
183✔
244
                                        description
183✔
245
                                      )}
183✔
246
                                    </li>
183✔
247
                                  );
248
                                })}
87✔
249
                              </ul>
87✔
250
                            ]
87✔
251
                          : null}
57✔
252
                      </div>
144✔
253
                    ]
144✔
254
                  : null}
6✔
255
              </div>
150✔
256
            );
257
          })}
95✔
258
        </div>
95✔
259
      </div>
95✔
260
    ) : null;
467✔
261
  }
562✔
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