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

code4recovery / tsml-ui / 18451009991

12 Oct 2025 11:29PM UTC coverage: 43.532% (-19.9%) from 63.458%
18451009991

Pull #475

github

web-flow
Merge 9d0374e51 into 0a0ddf96f
Pull Request #475: pretty permalinks

369 of 1009 branches covered (36.57%)

Branch coverage included in aggregate %.

15 of 37 new or added lines in 5 files covered. (40.54%)

236 existing lines in 17 files now uncovered.

553 of 1109 relevant lines covered (49.86%)

4.31 hits per line

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

0.0
/src/components/Table.tsx
1
import { useState } from 'react';
2

3
import InfiniteScroll from 'react-infinite-scroller';
4

5
import { useNavigate } from 'react-router-dom';
6

7
import { formatUrl, formatString as i18n } from '../helpers';
8
import { useData, useError, useFilter, useInput, useSettings } from '../hooks';
9
import {
10
  tableChicletCss,
11
  tableChicletsCss,
12
  tableInProgressCss,
13
  tableWrapperCss,
14
} from '../styles';
15
import { Meeting } from '../types';
16

17
import Icon, { icons } from './Icon';
18
import Link from './Link';
19

20
export default function Table() {
UNCOV
21
  const { capabilities, meetings } = useData();
×
UNCOV
22
  const { error } = useError();
×
UNCOV
23
  const { filteredSlugs, inProgress } = useFilter();
×
UNCOV
24
  const { settings, strings } = useSettings();
×
UNCOV
25
  const { input, latitude, longitude } = useInput();
×
UNCOV
26
  const navigate = useNavigate();
×
UNCOV
27
  const meetingsPerPage = 10;
×
UNCOV
28
  const supported_columns = [
×
29
    'address',
30
    'distance',
31
    'location',
32
    'location_group',
33
    'name',
34
    'region',
35
    'time',
36
  ];
UNCOV
37
  const [limit, setLimit] = useState(meetingsPerPage);
×
UNCOV
38
  const [showInProgress, setShowInProgress] = useState(false);
×
39

UNCOV
40
  if (error) {
×
41
    return null;
×
42
  }
43

UNCOV
44
  const { distance, location, region } = capabilities;
×
45

46
  //show columns based on capabilities
UNCOV
47
  const columns = settings.columns
×
UNCOV
48
    .filter(col => supported_columns.includes(col))
×
UNCOV
49
    .filter(col => region || col !== 'region')
×
UNCOV
50
    .filter(col => (distance && latitude && longitude) || col !== 'distance')
×
UNCOV
51
    .filter(col => location || !['location', 'location_group'].includes(col));
×
52

UNCOV
53
  const getValue = (meeting: Meeting, key: string) => {
×
UNCOV
54
    if (key === 'address') {
×
55
      const attendance: {
56
        icon: keyof typeof icons;
57
        text?: string;
58
        noTranslate?: boolean;
59
        type: 'in-person' | 'online' | 'inactive';
UNCOV
60
      }[] = [];
×
UNCOV
61
      if (meeting.isInPerson) {
×
UNCOV
62
        attendance.push({
×
63
          icon: 'geo',
64
          text: meeting.address,
65
          type: 'in-person',
66
          noTranslate: true,
67
        });
68
      }
UNCOV
69
      if (meeting.conference_provider) {
×
UNCOV
70
        attendance.push({
×
71
          icon: 'camera',
72
          text: meeting.conference_provider,
73
          type: 'online',
74
        });
75
      }
UNCOV
76
      if (meeting.conference_phone) {
×
77
        attendance.push({
×
78
          icon: 'phone',
79
          text: strings.phone,
80
          type: 'online',
81
        });
82
      }
UNCOV
83
      if (!meeting.isInPerson && !meeting.isOnline) {
×
UNCOV
84
        attendance.push({
×
85
          icon: 'close',
86
          text: strings.types.inactive,
87
          type: 'inactive',
88
        });
89
      }
UNCOV
90
      return (
×
91
        <div css={tableChicletsCss}>
92
          {attendance.map(({ icon, text, type, noTranslate }, index) => (
UNCOV
93
            <span css={tableChicletCss(type)} key={index}>
×
94
              <Icon icon={icon} size={18} />
95
              {text && (
×
96
                <span className={noTranslate ? 'notranslate' : undefined}>
×
97
                  {text}
98
                </span>
99
              )}
100
            </span>
101
          ))}
102
        </div>
103
      );
UNCOV
104
    } else if (key === 'distance' && meeting.distance) {
×
105
      return (
×
106
        <div>
107
          <span>{meeting.distance.toLocaleString(navigator.language)}</span>
108
          <small>{strings[settings.distance_unit]}</small>
109
        </div>
110
      );
UNCOV
111
    } else if (key === 'location') {
×
112
      return meeting.location;
×
UNCOV
113
    } else if (key === 'location_group') {
×
114
      return meeting.isInPerson ? meeting.location : meeting.group;
×
UNCOV
115
    } else if (key === 'name' && meeting.slug) {
×
UNCOV
116
      return <Link meeting={meeting} />;
×
UNCOV
117
    } else if (key === 'region' && meeting.regions) {
×
UNCOV
118
      return meeting.regions[meeting.regions.length - 1];
×
UNCOV
119
    } else if (key === 'time') {
×
UNCOV
120
      return meeting.start ? (
×
121
        <time>
122
          <span>{meeting.start.toFormat('t')}</span>
123
          <span>{meeting.start.toFormat('cccc')}</span>
124
        </time>
125
      ) : (
126
        strings.appointment
127
      );
128
    }
129
    return null;
×
130
  };
131

UNCOV
132
  const Row = ({ slug }: { slug: keyof typeof meetings }) => {
×
UNCOV
133
    const meeting = meetings[slug];
×
UNCOV
134
    return (
×
135
      <tr
136
        onClick={() =>
137
          navigate(formatUrl({ ...input, meeting: meeting.slug }, settings))
×
138
        }
139
      >
140
        {columns.map((column, index) => (
UNCOV
141
          <td className={`tsml-${column}`} key={index}>
×
142
            {getValue(meeting, column)}
143
          </td>
144
        ))}
145
      </tr>
146
    );
147
  };
148

UNCOV
149
  return !filteredSlugs?.length ? null : (
×
150
    <div css={tableWrapperCss}>
151
      <table>
152
        <thead>
153
          <tr>
154
            {columns.map((column, index) => (
UNCOV
155
              <th key={index}>
×
156
                {strings[column as keyof Translation] as string}
157
              </th>
158
            ))}
159
          </tr>
160
        </thead>
161
        {!!inProgress?.length && (
×
162
          <tbody css={tableInProgressCss}>
163
            {showInProgress ? (
×
164
              inProgress.map((slug, index) => <Row slug={slug} key={index} />)
×
165
            ) : (
166
              <tr>
167
                <td colSpan={columns.length}>
168
                  <button onClick={() => setShowInProgress(true)}>
×
169
                    {inProgress.length === 1
×
170
                      ? strings.in_progress_single
171
                      : i18n(strings.in_progress_multiple, {
172
                          count: inProgress.length,
173
                        })}
174
                  </button>
175
                </td>
176
              </tr>
177
            )}
178
          </tbody>
179
        )}
180
        <InfiniteScroll
181
          element="tbody"
182
          hasMore={filteredSlugs.length > limit}
183
          loadMore={() => setLimit(limit + meetingsPerPage)}
×
184
        >
185
          {filteredSlugs.slice(0, limit).map((slug, index) => (
UNCOV
186
            <Row slug={slug} key={index} />
×
187
          ))}
188
        </InfiniteScroll>
189
      </table>
190
    </div>
191
  );
192
}
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