1
0

AdvancedTable.vue 24 KB

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