AdvancedTable.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  1. <template>
  2. <div>
  3. <div
  4. class="table-outer-container"
  5. @mousemove="columnResizingMouseMove($event)"
  6. >
  7. <div class="table-header">
  8. <tippy
  9. v-if="filters.length > 0"
  10. :touch="true"
  11. :interactive="true"
  12. placement="bottom"
  13. theme="search"
  14. ref="search"
  15. trigger="click"
  16. >
  17. <a class="button is-info" @click.prevent="true">
  18. <i class="material-icons icon-with-button">search</i>
  19. Search
  20. </a>
  21. <template #content>
  22. <div
  23. v-for="(query, index) in advancedQuery"
  24. :key="`query-${index}`"
  25. class="advanced-query"
  26. >
  27. <div class="control select">
  28. <select
  29. v-model="query.filter"
  30. @change="changeQueryFilter(index)"
  31. >
  32. <option
  33. v-for="filter in filters"
  34. :key="filter.name"
  35. :value="filter"
  36. >
  37. {{ filter.displayName }}
  38. </option>
  39. </select>
  40. </div>
  41. <div class="control select">
  42. <select v-model="query.filterType">
  43. <option
  44. v-for="filterType in filterTypes(
  45. query.filter
  46. )"
  47. :key="filterType.name"
  48. :value="filterType.name"
  49. :selected="
  50. query.filter.defaultFilterType ===
  51. filterType.name
  52. "
  53. >
  54. {{ filterType.displayName }}
  55. </option>
  56. </select>
  57. </div>
  58. <p class="control is-expanded">
  59. <input
  60. v-model="query.data"
  61. class="input"
  62. type="text"
  63. placeholder="Search value"
  64. @keyup.enter="getData()"
  65. :disabled="!query.filterType"
  66. />
  67. </p>
  68. <div class="control">
  69. <button
  70. class="button material-icons is-success"
  71. @click="addQueryItem()"
  72. >
  73. control_point
  74. </button>
  75. </div>
  76. <div
  77. v-if="advancedQuery.length > 1"
  78. class="control"
  79. >
  80. <button
  81. class="button material-icons is-danger"
  82. @click="removeQueryItem(index)"
  83. >
  84. remove_circle_outline
  85. </button>
  86. </div>
  87. </div>
  88. <a class="button is-info" @click="getData()">
  89. <i class="material-icons icon-with-button"
  90. >search</i
  91. >
  92. Search
  93. </a>
  94. </template>
  95. </tippy>
  96. <tippy
  97. v-if="hidableSortedColumns.length > 0"
  98. :touch="true"
  99. :interactive="true"
  100. placement="bottom"
  101. theme="dropdown"
  102. ref="editColumns"
  103. trigger="click"
  104. >
  105. <a class="button is-info" @click.prevent="true">
  106. <i class="material-icons icon-with-button">tune</i>
  107. Columns
  108. </a>
  109. <template #content>
  110. <draggable
  111. item-key="name"
  112. v-model="orderedColumns"
  113. v-bind="columnDragOptions"
  114. tag="div"
  115. draggable=".item-draggable"
  116. class="nav-dropdown-items"
  117. >
  118. <template #item="{ element: column }">
  119. <button
  120. v-if="column.name !== 'select'"
  121. :class="{
  122. sortable: column.sortable,
  123. 'item-draggable': column.draggable,
  124. 'nav-item': true
  125. }"
  126. @click.prevent="
  127. toggleColumnVisibility(column)
  128. "
  129. >
  130. <p
  131. class="
  132. control
  133. is-expanded
  134. checkbox-control
  135. "
  136. >
  137. <label class="switch">
  138. <input
  139. v-if="column.hidable"
  140. type="checkbox"
  141. :id="index"
  142. :checked="
  143. shownColumns.indexOf(
  144. column.name
  145. ) !== -1
  146. "
  147. @click="
  148. toggleColumnVisibility(
  149. column
  150. )
  151. "
  152. />
  153. <span
  154. :class="{
  155. slider: true,
  156. round: true,
  157. disabled: !column.hidable
  158. }"
  159. ></span>
  160. </label>
  161. <label :for="index">
  162. <span></span>
  163. <p>{{ column.displayName }}</p>
  164. </label>
  165. </p>
  166. </button>
  167. </template>
  168. </draggable>
  169. </template>
  170. </tippy>
  171. </div>
  172. <div class="table-container">
  173. <table class="table">
  174. <thead>
  175. <draggable
  176. item-key="name"
  177. v-model="orderedColumns"
  178. v-bind="columnDragOptions"
  179. tag="tr"
  180. draggable=".item-draggable"
  181. >
  182. <template #item="{ element: column }">
  183. <th
  184. :class="{
  185. sortable: column.sortable,
  186. 'item-draggable': column.draggable
  187. }"
  188. :style="{
  189. minWidth: `${column.minWidth}px`,
  190. width: `${column.width}px`,
  191. maxWidth: `${column.maxWidth}px`
  192. }"
  193. v-if="
  194. shownColumns.indexOf(column.name) !== -1
  195. "
  196. >
  197. <div>
  198. <span>
  199. {{ column.displayName }}
  200. </span>
  201. <span
  202. v-if="column.draggable"
  203. content="Toggle Pinned Column"
  204. v-tippy
  205. @click="togglePinnedColumn(column)"
  206. >
  207. <span
  208. :class="{
  209. 'material-icons': true,
  210. active: false
  211. }"
  212. >
  213. push_pin
  214. </span>
  215. </span>
  216. <span
  217. v-if="column.sortable"
  218. :content="`Sort by ${column.displayName}`"
  219. v-tippy
  220. >
  221. <span
  222. v-if="
  223. !sort[column.sortProperty]
  224. "
  225. class="material-icons"
  226. @click="changeSort(column)"
  227. >
  228. unfold_more
  229. </span>
  230. <span
  231. v-if="
  232. sort[
  233. column.sortProperty
  234. ] === 'ascending'
  235. "
  236. class="material-icons active"
  237. @click="changeSort(column)"
  238. >
  239. expand_more
  240. </span>
  241. <span
  242. v-if="
  243. sort[
  244. column.sortProperty
  245. ] === 'descending'
  246. "
  247. class="material-icons active"
  248. @click="changeSort(column)"
  249. >
  250. expand_less
  251. </span>
  252. </span>
  253. </div>
  254. <div
  255. class="resizer"
  256. v-if="column.resizable"
  257. @mousedown.prevent.stop="
  258. columnResizingMouseDown(
  259. column,
  260. $event
  261. )
  262. "
  263. @mouseup="columnResizingMouseUp()"
  264. @dblclick="columnResetWidth(column)"
  265. ></div>
  266. </th>
  267. </template>
  268. </draggable>
  269. </thead>
  270. <tbody>
  271. <tr
  272. v-for="(item, itemIndex) in data"
  273. :key="item._id"
  274. :class="{
  275. selected: item.selected,
  276. highlighted: item.highlighted
  277. }"
  278. @click="clickItem(itemIndex, $event)"
  279. >
  280. <td
  281. v-for="column in sortedFilteredColumns"
  282. :key="`${item._id}-${column.name}`"
  283. >
  284. <slot
  285. :name="`column-${column.name}`"
  286. :item="item"
  287. v-if="
  288. column.properties.length === 0 ||
  289. column.properties.every(
  290. property =>
  291. item[property] !== undefined
  292. )
  293. "
  294. ></slot>
  295. <div
  296. class="resizer"
  297. v-if="column.resizable"
  298. @mousedown.prevent.stop="
  299. columnResizingMouseDown(column, $event)
  300. "
  301. @mouseup="columnResizingMouseUp()"
  302. @dblclick="columnResetWidth(column)"
  303. ></div>
  304. </td>
  305. </tr>
  306. </tbody>
  307. </table>
  308. </div>
  309. <div class="table-footer">
  310. <div class="page-controls">
  311. <button
  312. :class="{ disabled: page === 1 }"
  313. class="button is-primary material-icons"
  314. :disabled="page === 1"
  315. @click="changePage(1)"
  316. content="First Page"
  317. v-tippy
  318. >
  319. skip_previous
  320. </button>
  321. <button
  322. :class="{ disabled: page === 1 }"
  323. class="button is-primary material-icons"
  324. :disabled="page === 1"
  325. @click="changePage(page - 1)"
  326. content="Previous Page"
  327. v-tippy
  328. >
  329. fast_rewind
  330. </button>
  331. <p>Page {{ page }} / {{ lastPage }}</p>
  332. <button
  333. :class="{ disabled: page === lastPage }"
  334. class="button is-primary material-icons"
  335. :disabled="page === lastPage"
  336. @click="changePage(page + 1)"
  337. content="Next Page"
  338. v-tippy
  339. >
  340. fast_forward
  341. </button>
  342. <button
  343. :class="{ disabled: page === lastPage }"
  344. class="button is-primary material-icons"
  345. :disabled="page === lastPage"
  346. @click="changePage(lastPage)"
  347. content="Last Page"
  348. v-tippy
  349. >
  350. skip_next
  351. </button>
  352. </div>
  353. <div class="page-size">
  354. <div class="control">
  355. <label class="label">Items per page</label>
  356. <p class="control select">
  357. <select
  358. v-model.number="pageSize"
  359. @change="changePageSize()"
  360. >
  361. <option value="10">10</option>
  362. <option value="25">25</option>
  363. <option value="50">50</option>
  364. <option value="100">100</option>
  365. <option value="250">250</option>
  366. <option value="500">500</option>
  367. <option value="1000">1000</option>
  368. </select>
  369. </p>
  370. </div>
  371. </div>
  372. </div>
  373. </div>
  374. <div
  375. v-if="selectedRows.length > 0"
  376. class="bulk-popup"
  377. :style="{
  378. top: bulkPopup.top + 'px',
  379. left: bulkPopup.left + 'px'
  380. }"
  381. >
  382. <button
  383. class="button is-primary"
  384. :content="
  385. selectedRows.length === 1
  386. ? `${selectedRows.length} row selected`
  387. : `${selectedRows.length} rows selected`
  388. "
  389. v-tippy
  390. >
  391. {{ selectedRows.length }}
  392. </button>
  393. <slot name="bulk-actions" :item="selectedRows" />
  394. <div class="right">
  395. <slot name="bulk-actions-right" :item="selectedRows" />
  396. <span
  397. class="material-icons drag-icon"
  398. @mousedown.left="onDragBox"
  399. >
  400. drag_indicator
  401. </span>
  402. </div>
  403. </div>
  404. </div>
  405. </template>
  406. <script>
  407. import { mapGetters } from "vuex";
  408. import draggable from "vuedraggable";
  409. import Toast from "toasters";
  410. import ws from "@/ws";
  411. export default {
  412. components: {
  413. draggable
  414. },
  415. props: {
  416. /*
  417. Column properties:
  418. name: Unique lowercase name
  419. displayName: Nice name for the column header
  420. properties: The properties this column needs to show data
  421. sortable: Boolean for whether the order of a particular column can be changed
  422. sortProperty: The property the backend will sort on if this column gets sorted, e.g. title
  423. hidable: Boolean for whether a column can be hidden
  424. defaultVisibility: Default visibility for a column, either "shown" or "hidden"
  425. draggable: Boolean for whether a column can be dragged/reordered,
  426. resizable: Boolean for whether a column can be resized
  427. minWidth: Minimum width of column, e.g. 50px
  428. width: Width of column, e.g. 100px
  429. maxWidth: Maximum width of column, e.g. 150px
  430. */
  431. columnDefault: { type: Object, default: () => {} },
  432. columns: { type: Array, default: null },
  433. filters: { type: Array, default: null },
  434. dataAction: { type: String, default: null }
  435. },
  436. data() {
  437. return {
  438. page: 1,
  439. pageSize: 10,
  440. data: [],
  441. count: 0, // TODO Rename
  442. sort: {},
  443. orderedColumns: [],
  444. shownColumns: [],
  445. columnDragOptions() {
  446. return {
  447. animation: 200,
  448. group: "columns",
  449. disabled: false,
  450. ghostClass: "draggable-list-ghost",
  451. filter: ".ignore-elements",
  452. fallbackTolerance: 50
  453. };
  454. },
  455. advancedQuery: [],
  456. resizing: {},
  457. allFilterTypes: {
  458. contains: {
  459. name: "contains",
  460. displayName: "Contains"
  461. },
  462. exact: {
  463. name: "exact",
  464. displayName: "Exact"
  465. },
  466. regex: {
  467. name: "regex",
  468. displayName: "Regex"
  469. }
  470. },
  471. bulkPopup: {
  472. top: 0,
  473. left: 0,
  474. pos1: 0,
  475. pos2: 0,
  476. pos3: 0,
  477. pos4: 0
  478. }
  479. };
  480. },
  481. computed: {
  482. properties() {
  483. return Array.from(
  484. new Set(
  485. this.sortedFilteredColumns.flatMap(
  486. column => column.properties
  487. )
  488. )
  489. );
  490. },
  491. lastPage() {
  492. return Math.ceil(this.count / this.pageSize);
  493. },
  494. sortedFilteredColumns() {
  495. return this.orderedColumns.filter(
  496. column => this.shownColumns.indexOf(column.name) !== -1
  497. );
  498. },
  499. hidableSortedColumns() {
  500. return this.orderedColumns.filter(column => column.hidable);
  501. },
  502. lastSelectedItemIndex() {
  503. return this.data.findIndex(item => item.highlighted);
  504. },
  505. selectedRows() {
  506. return this.data.filter(data => data.selected);
  507. },
  508. ...mapGetters({
  509. socket: "websockets/getSocket"
  510. })
  511. },
  512. mounted() {
  513. const columns = [
  514. {
  515. name: "select",
  516. displayName: "",
  517. properties: [],
  518. sortable: false,
  519. hidable: false,
  520. draggable: false,
  521. resizable: false,
  522. minWidth: 5,
  523. width: 5,
  524. maxWidth: 5
  525. },
  526. ...this.columns
  527. ];
  528. this.orderedColumns = columns.map(column => ({
  529. ...this.columnDefault,
  530. ...column
  531. }));
  532. // A column will be shown if the defaultVisibility is set to shown, OR if the defaultVisibility is not set to shown and hidable is false
  533. this.shownColumns = columns
  534. .filter(column => column.defaultVisibility !== "hidden")
  535. .map(column => column.name);
  536. const pageSize = parseInt(localStorage.getItem("adminPageSize"));
  537. if (!Number.isNaN(pageSize)) this.pageSize = pageSize;
  538. if (this.filters.length > 0)
  539. this.advancedQuery.push({
  540. data: "",
  541. filter: {},
  542. filterType: ""
  543. });
  544. this.bulkPopup.top = document.body.clientHeight - 56;
  545. this.bulkPopup.left = document.body.clientWidth / 2 - 200;
  546. ws.onConnect(this.init);
  547. },
  548. methods: {
  549. init() {
  550. this.getData();
  551. },
  552. getData() {
  553. this.socket.dispatch(
  554. this.dataAction,
  555. this.page,
  556. this.pageSize,
  557. this.properties,
  558. this.sort,
  559. this.advancedQuery,
  560. res => {
  561. console.log(111, res);
  562. new Toast(res.message);
  563. if (res.status === "success") {
  564. const { data, count } = res.data;
  565. this.data = data;
  566. this.count = count;
  567. }
  568. }
  569. );
  570. },
  571. changePageSize() {
  572. this.getData();
  573. localStorage.setItem("adminPageSize", this.pageSize);
  574. },
  575. changePage(page) {
  576. if (page < 1) return;
  577. if (page > this.lastPage) return;
  578. if (page === this.page) return;
  579. this.page = page;
  580. this.getData();
  581. },
  582. changeSort(column) {
  583. if (column.sortable) {
  584. const { sortProperty } = column;
  585. if (this.sort[sortProperty] === undefined)
  586. this.sort[sortProperty] = "ascending";
  587. else if (this.sort[sortProperty] === "ascending")
  588. this.sort[sortProperty] = "descending";
  589. else if (this.sort[sortProperty] === "descending")
  590. delete this.sort[sortProperty];
  591. this.getData();
  592. }
  593. },
  594. toggleColumnVisibility(column) {
  595. if (this.shownColumns.indexOf(column.name) !== -1) {
  596. if (this.shownColumns.length <= 2)
  597. return new Toast(
  598. `Unable to hide column ${column.displayName}, there must be at least 1 visibile column`
  599. );
  600. this.shownColumns.splice(
  601. this.shownColumns.indexOf(column.name),
  602. 1
  603. );
  604. } else {
  605. this.shownColumns.push(column.name);
  606. }
  607. return this.getData();
  608. },
  609. clickItem(itemIndex, event) {
  610. const { shiftKey, ctrlKey } = event;
  611. // Shift was pressed, so attempt to select all items between the clicked item and last clicked item
  612. if (shiftKey) {
  613. // If there is a last clicked item
  614. if (this.lastSelectedItemIndex >= 0) {
  615. // Clicked item is lower than last item, so select upwards until it reaches the last selected item
  616. if (itemIndex > this.lastSelectedItemIndex) {
  617. for (
  618. let itemIndexUp = itemIndex;
  619. itemIndexUp > this.lastSelectedItemIndex;
  620. itemIndexUp -= 1
  621. ) {
  622. this.data[itemIndexUp].selected = true;
  623. }
  624. }
  625. // Clicked item is higher than last item, so select downwards until it reaches the last selected item
  626. else if (itemIndex < this.lastSelectedItemIndex) {
  627. for (
  628. let itemIndexDown = itemIndex;
  629. itemIndexDown < this.lastSelectedItemIndex;
  630. itemIndexDown += 1
  631. ) {
  632. this.data[itemIndexDown].selected = true;
  633. }
  634. }
  635. }
  636. }
  637. // Ctrl was pressed, so toggle selected on the clicked item
  638. else if (ctrlKey) {
  639. this.data[itemIndex].selected = !this.data[itemIndex].selected;
  640. }
  641. // Neither ctrl nor shift were pressed, so unselect all items and set the clicked item to selected
  642. else {
  643. this.data = this.data.map(item => ({
  644. ...item,
  645. selected: false
  646. }));
  647. this.data[itemIndex].selected = true;
  648. }
  649. // Set the last clicked item to no longer be highlighted, if it exists
  650. if (this.lastSelectedItemIndex >= 0)
  651. this.data[this.lastSelectedItemIndex].highlighted = false;
  652. // Set the clicked item to be highlighted
  653. this.data[itemIndex].highlighted = true;
  654. },
  655. addQueryItem() {
  656. if (this.filters.length > 0)
  657. this.advancedQuery.push({
  658. data: "",
  659. filter: {},
  660. filterType: ""
  661. });
  662. },
  663. removeQueryItem(index) {
  664. if (this.advancedQuery.length > 1)
  665. this.advancedQuery.splice(index, 1);
  666. },
  667. columnResizingMouseDown(column, event) {
  668. this.resizing.resizing = true;
  669. this.resizing.resizingColumn = column;
  670. this.resizing.width = event.target.parentElement.offsetWidth;
  671. this.resizing.lastX = event.x;
  672. },
  673. columnResizingMouseMove(event) {
  674. if (this.resizing.resizing) {
  675. if (event.buttons !== 1) {
  676. this.resizing.resizing = false;
  677. }
  678. this.resizing.width -= this.resizing.lastX - event.x;
  679. this.resizing.lastX = event.x;
  680. if (
  681. this.resizing.resizingColumn.minWidth &&
  682. this.resizing.resizingColumn.maxWidth
  683. ) {
  684. this.resizing.resizingColumn.width = Math.max(
  685. Math.min(
  686. this.resizing.resizingColumn.maxWidth,
  687. this.resizing.width
  688. ),
  689. this.resizing.resizingColumn.minWidth
  690. );
  691. } else if (this.resizing.resizingColumn.minWidth) {
  692. this.resizing.resizingColumn.width = Math.max(
  693. this.resizing.width,
  694. this.resizing.resizingColumn.minWidth
  695. );
  696. } else if (this.resizing.resizingColumn.maxWidth) {
  697. this.resizing.resizingColumn.width = Math.min(
  698. this.resizing.resizingColumn.maxWidth,
  699. this.resizing.width
  700. );
  701. } else {
  702. this.resizing.resizingColumn.width = this.resizing.width;
  703. }
  704. console.log(`New width: ${this.resizing.width}px`);
  705. }
  706. },
  707. columnResizingMouseUp() {
  708. this.resizing.resizing = false;
  709. },
  710. columnResetWidth(column) {
  711. // eslint-disable-next-line no-param-reassign
  712. column.minWidth = column.maxWidth = "";
  713. },
  714. filterTypes(filter) {
  715. if (!filter || !filter.filterTypes) return [];
  716. return filter.filterTypes.map(
  717. filterType => this.allFilterTypes[filterType]
  718. );
  719. },
  720. changeQueryFilter(index) {
  721. this.advancedQuery[index].filterType =
  722. this.advancedQuery[index].filter.defaultFilterType;
  723. },
  724. onDragBox(e) {
  725. const e1 = e || window.event;
  726. e1.preventDefault();
  727. this.bulkPopup.pos3 = e1.clientX;
  728. this.bulkPopup.pos4 = e1.clientY;
  729. document.onmousemove = e => {
  730. const e2 = e || window.event;
  731. e2.preventDefault();
  732. // calculate the new cursor position:
  733. this.bulkPopup.pos1 = this.bulkPopup.pos3 - e.clientX;
  734. this.bulkPopup.pos2 = this.bulkPopup.pos4 - e.clientY;
  735. this.bulkPopup.pos3 = e.clientX;
  736. this.bulkPopup.pos4 = e.clientY;
  737. // set the element's new position:
  738. this.bulkPopup.top -= this.bulkPopup.pos2;
  739. this.bulkPopup.left -= this.bulkPopup.pos1;
  740. if (this.bulkPopup.top < 0) this.bulkPopup.top = 0;
  741. if (this.bulkPopup.top > document.body.clientHeight - 50)
  742. this.bulkPopup.top = document.body.clientHeight - 50;
  743. if (this.bulkPopup.left < 0) this.bulkPopup.left = 0;
  744. if (this.bulkPopup.left > document.body.clientWidth - 400)
  745. this.bulkPopup.left = document.body.clientWidth - 400;
  746. };
  747. document.onmouseup = () => {
  748. document.onmouseup = null;
  749. document.onmousemove = null;
  750. };
  751. }
  752. }
  753. };
  754. </script>
  755. <style lang="scss" scoped>
  756. .night-mode {
  757. .table-outer-container {
  758. .table-container .table {
  759. &,
  760. thead th {
  761. background-color: var(--dark-grey-3);
  762. color: var(--light-grey-2);
  763. }
  764. tr {
  765. &:nth-child(even) {
  766. background-color: var(--dark-grey-2);
  767. }
  768. &:hover,
  769. &:focus,
  770. &.highlighted {
  771. background-color: var(--dark-grey-4);
  772. }
  773. }
  774. th,
  775. td {
  776. border-color: var(--dark-grey) !important;
  777. }
  778. }
  779. .table-header,
  780. .table-footer {
  781. background-color: var(--dark-grey-3);
  782. color: var(--light-grey-2);
  783. }
  784. }
  785. .bulk-popup {
  786. border: 0;
  787. background-color: var(--dark-grey-2);
  788. color: var(--white);
  789. .material-icons {
  790. color: var(--white);
  791. }
  792. }
  793. }
  794. .table-outer-container {
  795. border-radius: 5px;
  796. box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
  797. margin: 10px 0;
  798. overflow: hidden;
  799. .table-container {
  800. overflow-x: auto;
  801. table {
  802. border-collapse: separate;
  803. table-layout: fixed;
  804. thead {
  805. tr {
  806. th {
  807. height: 40px;
  808. line-height: 40px;
  809. border: 1px solid var(--light-grey-2);
  810. border-width: 1px 1px 1px 0;
  811. &:first-child,
  812. &:last-child {
  813. border-width: 1px 0 1px;
  814. }
  815. &.sortable {
  816. cursor: pointer;
  817. }
  818. & > div {
  819. display: flex;
  820. white-space: nowrap;
  821. & > span {
  822. margin-left: 5px;
  823. &:first-child {
  824. margin-left: 0;
  825. margin-right: auto;
  826. }
  827. & > .material-icons {
  828. font-size: 22px;
  829. position: relative;
  830. top: 6px;
  831. cursor: pointer;
  832. &.active {
  833. color: var(--primary-color);
  834. }
  835. &:hover,
  836. &:focus {
  837. filter: brightness(90%);
  838. }
  839. }
  840. }
  841. }
  842. }
  843. }
  844. }
  845. tbody {
  846. tr {
  847. &.selected td:first-child {
  848. border-left: 5px solid var(--primary-color);
  849. padding-left: 0;
  850. }
  851. &.highlighted {
  852. background-color: var(--light-grey);
  853. td:first-child {
  854. border-left: 5px solid var(--red);
  855. padding-left: 0;
  856. }
  857. }
  858. &.selected.highlighted td:first-child {
  859. border-left: 5px solid var(--green);
  860. padding-left: 0;
  861. }
  862. td {
  863. border: 1px solid var(--light-grey-2);
  864. border-width: 0 1px 1px 0;
  865. &:first-child,
  866. &:last-child {
  867. border-width: 0 0 1px;
  868. }
  869. }
  870. }
  871. }
  872. }
  873. table thead tr th,
  874. table tbody tr td {
  875. position: relative;
  876. white-space: nowrap;
  877. text-overflow: ellipsis;
  878. overflow: hidden;
  879. &:first-child {
  880. position: sticky;
  881. left: 0;
  882. z-index: 2;
  883. padding: 0;
  884. padding-left: 5px;
  885. }
  886. .resizer {
  887. height: 100%;
  888. width: 5px;
  889. background-color: transparent;
  890. cursor: col-resize;
  891. position: absolute;
  892. right: 0;
  893. top: 0;
  894. }
  895. }
  896. }
  897. .table-header,
  898. .table-footer {
  899. display: flex;
  900. flex-direction: row;
  901. flex-wrap: wrap;
  902. justify-content: space-between;
  903. line-height: 36px;
  904. background-color: var(--white);
  905. }
  906. .table-header > span > .button {
  907. margin: 5px;
  908. }
  909. .table-footer {
  910. .page-controls,
  911. .page-size > .control {
  912. display: flex;
  913. flex-direction: row;
  914. margin-bottom: 0 !important;
  915. button {
  916. margin: 5px;
  917. font-size: 20px;
  918. }
  919. p,
  920. label {
  921. margin: 5px;
  922. font-size: 14px;
  923. font-weight: 600;
  924. }
  925. &.select::after {
  926. top: 18px;
  927. }
  928. }
  929. }
  930. }
  931. .advanced-query {
  932. display: flex;
  933. margin-bottom: 5px;
  934. & > .control {
  935. & > input,
  936. & > select,
  937. & > .button {
  938. border-radius: 0;
  939. }
  940. &:first-child {
  941. & > input,
  942. & > select,
  943. & > .button {
  944. border-radius: 5px 0 0 5px;
  945. }
  946. }
  947. &:last-child {
  948. & > input,
  949. & > select,
  950. & > .button {
  951. border-radius: 0 5px 5px 0;
  952. }
  953. }
  954. & > .button {
  955. font-size: 22px;
  956. }
  957. }
  958. }
  959. .bulk-popup {
  960. display: flex;
  961. position: fixed;
  962. flex-direction: row;
  963. width: 100%;
  964. max-width: 400px;
  965. line-height: 36px;
  966. z-index: 5;
  967. border: 1px solid var(--light-grey-3);
  968. border-radius: 5px;
  969. box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
  970. background-color: var(--white);
  971. color: var(--dark-grey);
  972. padding: 5px;
  973. .right {
  974. margin-left: auto;
  975. }
  976. .drag-icon {
  977. position: relative;
  978. top: 5px;
  979. color: var(--dark-grey);
  980. cursor: move;
  981. }
  982. }
  983. </style>