mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Fixed table rendering issues
This commit is contained in:
parent
0f43ad59a0
commit
74635281de
5 changed files with 133 additions and 130 deletions
|
@ -28,7 +28,7 @@ a,
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:not(.nav-link):not(.navbar-brand):not(.page-link):hover,
|
a:not(.nav-link):not(.navbar-brand):not(.page-link):not(.highlight-card):hover,
|
||||||
.btn-link:hover {
|
.btn-link:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,8 @@ hr {
|
||||||
.close,
|
.close,
|
||||||
.close:hover,
|
.close:hover,
|
||||||
.table,
|
.table,
|
||||||
.table-hover > tbody > tr:hover > * {
|
.table-hover > tbody > tr:hover > *,
|
||||||
|
.table-hover > tbody > tr > * {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,13 @@ const Settings = (
|
||||||
<NavPills className="mb-3">
|
<NavPills className="mb-3">
|
||||||
<NavPillItem to="general">General</NavPillItem>
|
<NavPillItem to="general">General</NavPillItem>
|
||||||
<NavPillItem to="short-urls">Short URLs</NavPillItem>
|
<NavPillItem to="short-urls">Short URLs</NavPillItem>
|
||||||
<NavPillItem to="secondary-items">Secondary items</NavPillItem>
|
<NavPillItem to="other-items">Other items</NavPillItem>
|
||||||
</NavPills>
|
</NavPills>
|
||||||
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="general" element={<SettingsSections items={[ <UserInterface key="one" />, <RealTimeUpdates key="two" /> ]} />} />
|
<Route path="general" element={<SettingsSections items={[ <UserInterface key="one" />, <RealTimeUpdates key="two" /> ]} />} />
|
||||||
<Route path="short-urls" element={<SettingsSections items={[ <ShortUrlCreation key="one" />, <ShortUrlsList key="two" /> ]} />} />
|
<Route path="short-urls" element={<SettingsSections items={[ <ShortUrlCreation key="one" />, <ShortUrlsList key="two" /> ]} />} />
|
||||||
<Route path="secondary-items" element={<SettingsSections items={[ <Tags key="one" />, <Visits key="two" /> ]} />} />
|
<Route path="other-items" element={<SettingsSections items={[ <Tags key="one" />, <Visits key="two" /> ]} />} />
|
||||||
<Route path="*" element={<Navigate replace to="general" />} />
|
<Route path="*" element={<Navigate replace to="general" />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</NoMenuLayout>
|
</NoMenuLayout>
|
||||||
|
|
|
@ -87,137 +87,139 @@ const VisitsTable = ({
|
||||||
}, [ searchTerm ]);
|
}, [ searchTerm ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className="table table-bordered table-hover table-sm table-responsive-sm visits-table">
|
<div className="table-responsive-md">
|
||||||
<thead className="visits-table__header">
|
<table className="table table-bordered table-hover table-sm visits-table">
|
||||||
<tr>
|
<thead className="visits-table__header">
|
||||||
<th
|
|
||||||
className={`${headerCellsClass} text-center`}
|
|
||||||
onClick={() => setSelectedVisits(
|
|
||||||
selectedVisits.length < resultSet.total ? resultSet.visitsGroups.flat() : [],
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={checkIcon} className={classNames({ 'text-primary': selectedVisits.length > 0 })} />
|
|
||||||
</th>
|
|
||||||
{supportsBots && (
|
|
||||||
<th className={`${headerCellsClass} text-center`} onClick={orderByColumn('potentialBot')}>
|
|
||||||
<FontAwesomeIcon icon={botIcon} />
|
|
||||||
{renderOrderIcon('potentialBot')}
|
|
||||||
</th>
|
|
||||||
)}
|
|
||||||
<th className={headerCellsClass} onClick={orderByColumn('date')}>
|
|
||||||
Date
|
|
||||||
{renderOrderIcon('date')}
|
|
||||||
</th>
|
|
||||||
<th className={headerCellsClass} onClick={orderByColumn('country')}>
|
|
||||||
Country
|
|
||||||
{renderOrderIcon('country')}
|
|
||||||
</th>
|
|
||||||
<th className={headerCellsClass} onClick={orderByColumn('city')}>
|
|
||||||
City
|
|
||||||
{renderOrderIcon('city')}
|
|
||||||
</th>
|
|
||||||
<th className={headerCellsClass} onClick={orderByColumn('browser')}>
|
|
||||||
Browser
|
|
||||||
{renderOrderIcon('browser')}
|
|
||||||
</th>
|
|
||||||
<th className={headerCellsClass} onClick={orderByColumn('os')}>
|
|
||||||
OS
|
|
||||||
{renderOrderIcon('os')}
|
|
||||||
</th>
|
|
||||||
<th className={headerCellsClass} onClick={orderByColumn('referer')}>
|
|
||||||
Referrer
|
|
||||||
{renderOrderIcon('referer')}
|
|
||||||
</th>
|
|
||||||
{isOrphanVisits && (
|
|
||||||
<th className={headerCellsClass} onClick={orderByColumn('visitedUrl')}>
|
|
||||||
Visited URL
|
|
||||||
{renderOrderIcon('visitedUrl')}
|
|
||||||
</th>
|
|
||||||
)}
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colSpan={fullSizeColSpan} className="p-0">
|
|
||||||
<SearchField noBorder large={false} onChange={setSearchTerm} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{!resultSet.visitsGroups[page - 1]?.length && (
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={fullSizeColSpan} className="text-center">
|
<th
|
||||||
No visits found with current filtering
|
className={`${headerCellsClass} text-center`}
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{resultSet.visitsGroups[page - 1]?.map((visit, index) => {
|
|
||||||
const isSelected = selectedVisits.includes(visit);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr
|
|
||||||
key={index}
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
className={classNames({ 'table-active': isSelected })}
|
|
||||||
onClick={() => setSelectedVisits(
|
onClick={() => setSelectedVisits(
|
||||||
isSelected ? selectedVisits.filter((v) => v !== visit) : [ ...selectedVisits, visit ],
|
selectedVisits.length < resultSet.total ? resultSet.visitsGroups.flat() : [],
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<td className="text-center">
|
<FontAwesomeIcon icon={checkIcon} className={classNames({ 'text-primary': selectedVisits.length > 0 })} />
|
||||||
{isSelected && <FontAwesomeIcon icon={checkIcon} className="text-primary" />}
|
</th>
|
||||||
</td>
|
{supportsBots && (
|
||||||
{supportsBots && (
|
<th className={`${headerCellsClass} text-center`} onClick={orderByColumn('potentialBot')}>
|
||||||
<td className="text-center">
|
<FontAwesomeIcon icon={botIcon} />
|
||||||
{visit.potentialBot && (
|
{renderOrderIcon('potentialBot')}
|
||||||
<>
|
</th>
|
||||||
<FontAwesomeIcon icon={botIcon} id={`botIcon${index}`} />
|
)}
|
||||||
<UncontrolledTooltip placement="right" target={`botIcon${index}`}>
|
<th className={headerCellsClass} onClick={orderByColumn('date')}>
|
||||||
Potentially a visit from a bot or crawler
|
Date
|
||||||
</UncontrolledTooltip>
|
{renderOrderIcon('date')}
|
||||||
</>
|
</th>
|
||||||
)}
|
<th className={headerCellsClass} onClick={orderByColumn('country')}>
|
||||||
</td>
|
Country
|
||||||
)}
|
{renderOrderIcon('country')}
|
||||||
<td><Time date={visit.date} /></td>
|
</th>
|
||||||
<td>{visit.country}</td>
|
<th className={headerCellsClass} onClick={orderByColumn('city')}>
|
||||||
<td>{visit.city}</td>
|
City
|
||||||
<td>{visit.browser}</td>
|
{renderOrderIcon('city')}
|
||||||
<td>{visit.os}</td>
|
</th>
|
||||||
<td>{visit.referer}</td>
|
<th className={headerCellsClass} onClick={orderByColumn('browser')}>
|
||||||
{isOrphanVisits && <td>{(visit as NormalizedOrphanVisit).visitedUrl}</td>}
|
Browser
|
||||||
</tr>
|
{renderOrderIcon('browser')}
|
||||||
);
|
</th>
|
||||||
})}
|
<th className={headerCellsClass} onClick={orderByColumn('os')}>
|
||||||
</tbody>
|
OS
|
||||||
{resultSet.total > PAGE_SIZE && (
|
{renderOrderIcon('os')}
|
||||||
<tfoot>
|
</th>
|
||||||
|
<th className={headerCellsClass} onClick={orderByColumn('referer')}>
|
||||||
|
Referrer
|
||||||
|
{renderOrderIcon('referer')}
|
||||||
|
</th>
|
||||||
|
{isOrphanVisits && (
|
||||||
|
<th className={headerCellsClass} onClick={orderByColumn('visitedUrl')}>
|
||||||
|
Visited URL
|
||||||
|
{renderOrderIcon('visitedUrl')}
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={fullSizeColSpan} className="visits-table__footer-cell visits-table__sticky">
|
<td colSpan={fullSizeColSpan} className="p-0">
|
||||||
<div className="row">
|
<SearchField noBorder large={false} onChange={setSearchTerm} />
|
||||||
<div className="col-md-6">
|
|
||||||
<SimplePaginator
|
|
||||||
pagesCount={Math.ceil(resultSet.total / PAGE_SIZE)}
|
|
||||||
currentPage={page}
|
|
||||||
setCurrentPage={setPage}
|
|
||||||
centered={isMobileDevice}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={classNames('col-md-6', {
|
|
||||||
'd-flex align-items-center flex-row-reverse': !isMobileDevice,
|
|
||||||
'text-center mt-3': isMobileDevice,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
Visits <b>{prettify(start + 1)}</b> to{' '}
|
|
||||||
<b>{prettify(min(end, resultSet.total))}</b> of{' '}
|
|
||||||
<b>{prettify(resultSet.total)}</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</thead>
|
||||||
)}
|
<tbody>
|
||||||
</table>
|
{!resultSet.visitsGroups[page - 1]?.length && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={fullSizeColSpan} className="text-center">
|
||||||
|
No visits found with current filtering
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
{resultSet.visitsGroups[page - 1]?.map((visit, index) => {
|
||||||
|
const isSelected = selectedVisits.includes(visit);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
key={index}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
className={classNames({ 'table-active': isSelected })}
|
||||||
|
onClick={() => setSelectedVisits(
|
||||||
|
isSelected ? selectedVisits.filter((v) => v !== visit) : [ ...selectedVisits, visit ],
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<td className="text-center">
|
||||||
|
{isSelected && <FontAwesomeIcon icon={checkIcon} className="text-primary" />}
|
||||||
|
</td>
|
||||||
|
{supportsBots && (
|
||||||
|
<td className="text-center">
|
||||||
|
{visit.potentialBot && (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon icon={botIcon} id={`botIcon${index}`} />
|
||||||
|
<UncontrolledTooltip placement="right" target={`botIcon${index}`}>
|
||||||
|
Potentially a visit from a bot or crawler
|
||||||
|
</UncontrolledTooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
<td><Time date={visit.date} /></td>
|
||||||
|
<td>{visit.country}</td>
|
||||||
|
<td>{visit.city}</td>
|
||||||
|
<td>{visit.browser}</td>
|
||||||
|
<td>{visit.os}</td>
|
||||||
|
<td>{visit.referer}</td>
|
||||||
|
{isOrphanVisits && <td>{(visit as NormalizedOrphanVisit).visitedUrl}</td>}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
{resultSet.total > PAGE_SIZE && (
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colSpan={fullSizeColSpan} className="visits-table__footer-cell visits-table__sticky">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-6">
|
||||||
|
<SimplePaginator
|
||||||
|
pagesCount={Math.ceil(resultSet.total / PAGE_SIZE)}
|
||||||
|
currentPage={page}
|
||||||
|
setCurrentPage={setPage}
|
||||||
|
centered={isMobileDevice}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames('col-md-6', {
|
||||||
|
'd-flex align-items-center flex-row-reverse': !isMobileDevice,
|
||||||
|
'text-center mt-3': isMobileDevice,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
Visits <b>{prettify(start + 1)}</b> to{' '}
|
||||||
|
<b>{prettify(min(end, resultSet.total))}</b> of{' '}
|
||||||
|
<b>{prettify(resultSet.total)}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
)}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { DropdownItem, DropdownItemProps } from 'reactstrap'; // eslint-disable-line import/named
|
import { DropdownItem, DropdownItemProps } from 'reactstrap';
|
||||||
import { OrphanVisitType, VisitsFilter } from '../types';
|
import { OrphanVisitType, VisitsFilter } from '../types';
|
||||||
import { DropdownBtn } from '../../utils/DropdownBtn';
|
import { DropdownBtn } from '../../utils/DropdownBtn';
|
||||||
import { hasValue } from '../../utils/utils';
|
import { hasValue } from '../../utils/utils';
|
||||||
|
|
|
@ -24,6 +24,6 @@ describe('<Settings />', () => {
|
||||||
expect(items).toHaveLength(3);
|
expect(items).toHaveLength(3);
|
||||||
expect(items.first().prop('to')).toEqual('general');
|
expect(items.first().prop('to')).toEqual('general');
|
||||||
expect(items.at(1).prop('to')).toEqual('short-urls');
|
expect(items.at(1).prop('to')).toEqual('short-urls');
|
||||||
expect(items.last().prop('to')).toEqual('secondary-items');
|
expect(items.last().prop('to')).toEqual('other-items');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue