gitscope
← marzneshin / v0.1.0 / app/dashboard/src/pages/users/table/index.tsx
https://dev.kronoz.win/r/marzneshin.git
app/dashboard/src/pages/users/table/index.tsx · TypeScript
Blame Raw
  1
  2import {
  3  Accordion,
  4  AccordionButton,
  5  AccordionItem,
  6  AccordionPanel,
  7  Box,
  8  HStack,
  9  IconButton,
 10  Table,
 11  TableProps,
 12  Tbody,
 13  Td,
 14  Text,
 15  Th,
 16  Thead,
 17  Tooltip,
 18  Tr,
 19  useBreakpointValue,
 20  VStack,
 21} from '@chakra-ui/react';
 22import classNames from 'classnames';
 23import { statusColors } from 'constants/Settings';
 24import { FC, Fragment, useState } from 'react';
 25import { useTranslation } from 'react-i18next';
 26import { OnlineBadge } from 'components/online-badge';
 27import { OnlineStatus } from 'components/online-status';
 28import { StatusBadge } from 'components/status-badge';
 29import {
 30  Pagination,
 31  EmptySection,
 32  AccordionArrowIcon,
 33  handleSort,
 34  EditIcon,
 35  StatusSortSelect,
 36  Sort,
 37} from 'components/table';
 38import { UsageSlider, UsageSliderCompact } from './usage-slider';
 39import { useUsers } from 'stores';
 40import { ActionButtons } from './action-buttons';
 41import { pageSizeManagers } from 'utils/userPreferenceStorage';
 42
 43type ExpandedIndex = number | number[];
 44
 45type UsersTableProps = {} & TableProps;
 46
 47export const UsersTable: FC<UsersTableProps> = (props) => {
 48  const {
 49    usersFilters: filters,
 50    users: { users },
 51    users: { total },
 52    onEditingUser,
 53    onCreateUser,
 54    onFilterChange,
 55  } = useUsers();
 56
 57  const { t } = useTranslation();
 58  const [selectedRow, setSelectedRow] = useState<ExpandedIndex | undefined>(
 59    undefined
 60  );
 61  const useTable = useBreakpointValue({ base: false, md: true });
 62
 63  const isFiltered = users.length !== total;
 64
 65  const handleStatusFilter = (e: any) => {
 66    onFilterChange({
 67      status: e.target.value.length > 0 ? e.target.value : undefined,
 68    });
 69  };
 70
 71  const toggleAccordion = (index: number) => {
 72    setSelectedRow(index === selectedRow ? undefined : index);
 73  };
 74  return (
 75    <Box id="users-table" overflowX={{ base: 'unset', md: 'unset' }}>
 76      <Accordion
 77        allowMultiple
 78        display={{ base: 'block', md: 'none' }}
 79        index={selectedRow}
 80      >
 81        <Table orientation="vertical" zIndex="docked" {...props}>
 82          <Thead zIndex="docked" position="relative">
 83            <Tr>
 84              <Th
 85                position="sticky"
 86                minW="120px"
 87                pl={4}
 88                pr={4}
 89                cursor={'pointer'}
 90                onClick={handleSort.bind(null, filters, 'username', onFilterChange)}
 91              >
 92                <HStack>
 93                  <span>{t('users')}</span>
 94                  <Sort sort={filters.sort} column="username" />
 95                </HStack>
 96              </Th>
 97              <Th
 98                position="sticky"
 99                minW="50px"
100                pl={0}
101                pr={0}
102                w="140px"
103                cursor={'pointer'}
104              >
105                <HStack spacing={0} position="relative">
106                  <Text
107                    position="absolute"
108                    _dark={{
109                      bg: 'gray.750',
110                    }}
111                    _light={{
112                      bg: '#F9FAFB',
113                    }}
114                    userSelect="none"
115                    pointerEvents="none"
116                    zIndex={1}
117                    w="100%"
118                  >
119                    {t('usersTable.status')}
120                    {filters.status ? ': ' + filters.status : ''}
121                  </Text>
122                  <StatusSortSelect filters={filters} handleStatusFilter={handleStatusFilter} options={['active', 'disabled', 'limited', 'expired', 'on_hold']} />
123                </HStack>
124              </Th>
125              <Th
126                position="sticky"
127                minW="100px"
128                cursor={'pointer'}
129                pr={0}
130                onClick={handleSort.bind(null, filters, 'used_traffic', onFilterChange)}
131              >
132                <HStack>
133                  <span>{t('usersTable.dataUsage')}</span>
134                  <Sort sort={filters.sort} column="used_traffic" />
135                </HStack>
136              </Th>
137              <Th
138                position="sticky"
139                minW="32px"
140                w="32px"
141                p={0}
142                cursor={'pointer'}
143              ></Th>
144            </Tr>
145          </Thead>
146          <Tbody>
147            {!useTable &&
148              users?.map((user, i) => {
149                return (
150                  <Fragment key={user.username}>
151                    <Tr
152                      onClick={toggleAccordion.bind(null, i)}
153                      cursor="pointer"
154                    >
155                      <Td borderBottom={0} minW="100px" pl={4} pr={4}>
156                        <div className="flex-status">
157                          <OnlineBadge lastOnline={user.online_at} />
158                          {user.username}
159                        </div>
160                      </Td>
161                      <Td borderBottom={0} minW="50px" pl={0} pr={0}>
162                        <StatusBadge
163                          compact
164                          showDetail={false}
165                          expiryDate={user.expire}
166                          status={user.status}
167                        />
168                      </Td>
169                      <Td borderBottom={0} minW="100px" pr={0}>
170                        <UsageSliderCompact
171                          totalUsedTraffic={user.lifetime_used_traffic}
172                          dataLimitResetStrategy={
173                            user.data_limit_reset_strategy
174                          }
175                          used={user.used_traffic}
176                          total={user.data_limit}
177                          colorScheme={statusColors[user.status].bandWidthColor}
178                        />
179                      </Td>
180                      <Td p={0} borderBottom={0} w="32px" minW="32px">
181                        <AccordionArrowIcon
182                          color="gray.600"
183                          _dark={{
184                            color: 'gray.400',
185                          }}
186                          transition="transform .2s ease-out"
187                          transform={
188                            selectedRow === i ? 'rotate(180deg)' : '0deg'
189                          }
190                        />
191                      </Td>
192                    </Tr>
193                    <Tr
194                      className="collapsible"
195                      onClick={toggleAccordion.bind(null, i)}
196                    >
197                      <Td p={0} colSpan={4}>
198                        <AccordionItem border={0}>
199                          <AccordionButton display="none"></AccordionButton>
200                          <AccordionPanel
201                            border={0}
202                            cursor="pointer"
203                            px={6}
204                            py={3}
205                          >
206                            <VStack justifyContent="space-between" spacing="4">
207                              <VStack
208                                alignItems="flex-start"
209                                w="full"
210                                spacing={-1}
211                              >
212                                <Text
213                                  textTransform="capitalize"
214                                  fontSize="xs"
215                                  fontWeight="bold"
216                                  color="gray.600"
217                                  _dark={{
218                                    color: 'gray.400',
219                                  }}
220                                >
221                                  {t('usersTable.dataUsage')}
222                                </Text>
223                                <Box width="full" minW="230px">
224                                  <UsageSlider
225                                    totalUsedTraffic={
226                                      user.lifetime_used_traffic
227                                    }
228                                    dataLimitResetStrategy={
229                                      user.data_limit_reset_strategy
230                                    }
231                                    used={user.used_traffic}
232                                    total={user.data_limit}
233                                    colorScheme={
234                                      statusColors[user.status].bandWidthColor
235                                    }
236                                  />
237                                </Box>
238                              </VStack>
239                              <HStack w="full" justifyContent="space-between">
240                                <Box width="full">
241                                  <StatusBadge
242                                    compact
243                                    expiryDate={user.expire}
244                                    status={user.status}
245                                  />
246                                  <OnlineStatus lastOnline={user.online_at} />
247                                </Box>
248                                <HStack>
249                                  <ActionButtons user={user} />
250                                  <Tooltip
251                                    label={t('userDialog.editUser')}
252                                    placement="top"
253                                  >
254                                    <IconButton
255                                      p="0 !important"
256                                      aria-label="Edit user"
257                                      bg="transparent"
258                                      _dark={{
259                                        _hover: {
260                                          bg: 'gray.700',
261                                        },
262                                      }}
263                                      size={{
264                                        base: 'sm',
265                                        md: 'md',
266                                      }}
267                                      onClick={(e) => {
268                                        e.stopPropagation();
269                                        onEditingUser(user);
270                                      }}
271                                    >
272                                      <EditIcon />
273                                    </IconButton>
274                                  </Tooltip>
275                                </HStack>
276                              </HStack>
277                            </VStack>
278                          </AccordionPanel>
279                        </AccordionItem>
280                      </Td>
281                    </Tr>
282                  </Fragment>
283                );
284              })}
285          </Tbody>
286        </Table>
287      </Accordion>
288      <Table
289        orientation="vertical"
290        display={{ base: 'none', md: 'table' }}
291        {...props}
292      >
293        <Thead zIndex="docked" position="relative">
294          <Tr>
295            <Th
296              position="sticky"
297              minW="140px"
298              cursor={'pointer'}
299              onClick={handleSort.bind(null, filters, 'username', onFilterChange)}
300            >
301              <HStack>
302                <span>{t('username')}</span>
303                <Sort sort={filters.sort} column="username" />
304              </HStack>
305            </Th>
306            <Th
307              position="sticky"
308              width="400px"
309              minW="150px"
310              cursor={'pointer'}
311            >
312              <HStack spacing={0} position="relative">
313                <Text
314                  position="absolute"
315                  _dark={{
316                    bg: 'gray.750',
317                  }}
318                  _light={{
319                    bg: '#F9FAFB',
320                  }}
321                  userSelect="none"
322                  pointerEvents="none"
323                  zIndex={1}
324                  w="100%"
325                >
326                  {t('usersTable.status')}
327                  {filters.status ? ': ' + filters.status : ''}
328                </Text>
329                <StatusSortSelect filters={filters} handleStatusFilter={handleStatusFilter} options={['active', 'disabled', 'limited', 'expired', 'on_hold']} />
330              </HStack>
331            </Th>
332            <Th
333              position="sticky"
334              width="350px"
335              minW="230px"
336              cursor={'pointer'}
337              onClick={handleSort.bind(null, filters, 'used_traffic', onFilterChange)}
338            >
339              <HStack>
340                <span>{t('usersTable.dataUsage')}</span>
341                <Sort sort={filters.sort} column="used_traffic" />
342              </HStack>
343            </Th>
344            <Th
345              position="sticky"
346              width="200px"
347              minW="180px"
348            />
349          </Tr>
350        </Thead>
351        <Tbody>
352          {useTable &&
353            users?.map((user, i) => {
354              return (
355                <Tr
356                  key={user.username}
357                  className={classNames('interactive', {
358                    'last-row': i === users.length - 1,
359                  })}
360                  onClick={() => onEditingUser(user)}
361                >
362                  <Td minW="140px">
363                    <div className="flex-status">
364                      <OnlineBadge lastOnline={user.online_at} />
365                      {user.username}
366                      <OnlineStatus lastOnline={user.online_at} />
367                    </div>
368                  </Td>
369                  <Td width="400px" minW="150px">
370                    <StatusBadge
371                      expiryDate={user.expire}
372                      status={user.status}
373                    />
374                  </Td>
375                  <Td width="350px" minW="230px">
376                    <UsageSlider
377                      totalUsedTraffic={user.lifetime_used_traffic}
378                      dataLimitResetStrategy={user.data_limit_reset_strategy}
379                      used={user.used_traffic}
380                      total={user.data_limit}
381                      colorScheme={statusColors[user.status].bandWidthColor}
382                    />
383                  </Td>
384                  <Td width="200px" minW="180px">
385                    <ActionButtons user={user} />
386                  </Td>
387                </Tr>
388              );
389            })}
390          {users.length == 0 && (
391            <Tr>
392              <Td colSpan={4}>
393                <EmptySection
394                  isFiltered={isFiltered}
395                  noObjectT="usersTable.noUser"
396                  noObjectMatchedT='usersTable.noUserMatched'
397                  createObjectT="createUser"
398                  onCreateObject={onCreateUser}
399                />
400              </Td>
401            </Tr>
402          )}
403        </Tbody>
404      </Table>
405      <Pagination filters={filters} total={total} onFilterChange={onFilterChange} pageSizeManager={pageSizeManagers.users} />
406    </Box>
407  );
408};