From 6712bd2fb392befd566644f9022ffd22ec565ff0 Mon Sep 17 00:00:00 2001 From: Arpit Date: Mon, 26 Dec 2022 17:09:12 +0530 Subject: [PATCH] Internal storage - Pagination for Database table (#5040) * init * footer component with pagintion ui basic styles * pagination: apply a limit and offset rows through query params * open create row drawer from footer button * border color for dark theme footer button * cleaned * pagination * fixes: input value * moved functions to component level --- frontend/assets/images/icons/add-row.svg | 8 ++ frontend/assets/images/icons/chevron-left.svg | 4 + .../assets/images/icons/chevron-right.svg | 4 + .../Drawers/CreateRowDrawer/index.jsx | 5 +- frontend/src/TooljetDatabase/Table/Footer.jsx | 119 ++++++++++++++++++ .../src/TooljetDatabase/Table/Paginations.jsx | 65 ++++++++++ frontend/src/TooljetDatabase/Table/index.jsx | 31 +++-- .../TooljetDatabasePage/index.jsx | 10 +- frontend/src/_styles/components.scss | 8 ++ frontend/src/_styles/theme.scss | 19 +++ frontend/src/_ui/Select/SelectComponent.jsx | 3 +- .../src/services/postgrest_proxy.service.ts | 3 + 12 files changed, 265 insertions(+), 14 deletions(-) create mode 100644 frontend/assets/images/icons/add-row.svg create mode 100644 frontend/assets/images/icons/chevron-left.svg create mode 100644 frontend/assets/images/icons/chevron-right.svg create mode 100644 frontend/src/TooljetDatabase/Table/Footer.jsx create mode 100644 frontend/src/TooljetDatabase/Table/Paginations.jsx diff --git a/frontend/assets/images/icons/add-row.svg b/frontend/assets/images/icons/add-row.svg new file mode 100644 index 0000000000..2201744be3 --- /dev/null +++ b/frontend/assets/images/icons/add-row.svg @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/frontend/assets/images/icons/chevron-left.svg b/frontend/assets/images/icons/chevron-left.svg new file mode 100644 index 0000000000..99e04063fe --- /dev/null +++ b/frontend/assets/images/icons/chevron-left.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/assets/images/icons/chevron-right.svg b/frontend/assets/images/icons/chevron-right.svg new file mode 100644 index 0000000000..85d0073374 --- /dev/null +++ b/frontend/assets/images/icons/chevron-right.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx b/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx index 25943aca46..31d474e607 100644 --- a/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx +++ b/frontend/src/TooljetDatabase/Drawers/CreateRowDrawer/index.jsx @@ -1,13 +1,12 @@ -import React, { useState, useContext } from 'react'; +import React, { useContext } from 'react'; import Drawer from '@/_ui/Drawer'; import { toast } from 'react-hot-toast'; import CreateRowForm from '../../Forms/RowForm'; import { TooljetDatabaseContext } from '../../index'; import { tooljetDatabaseService } from '@/_services'; -const CreateRowDrawer = () => { +const CreateRowDrawer = ({ isCreateRowDrawerOpen, setIsCreateRowDrawerOpen }) => { const { organizationId, selectedTable, setSelectedTableData } = useContext(TooljetDatabaseContext); - const [isCreateRowDrawerOpen, setIsCreateRowDrawerOpen] = useState(false); return ( <> diff --git a/frontend/src/TooljetDatabase/Table/Footer.jsx b/frontend/src/TooljetDatabase/Table/Footer.jsx new file mode 100644 index 0000000000..120d257ea8 --- /dev/null +++ b/frontend/src/TooljetDatabase/Table/Footer.jsx @@ -0,0 +1,119 @@ +import React, { useState } from 'react'; +import { Button } from '@/_ui/LeftSidebar'; +import Select from '@/_ui/Select'; +import Pagination from './Paginations'; + +const Footer = ({ darkMode, openCreateRowDrawer, totalRecords, fetchTableData }) => { + const selectOptions = [ + { label: '50 records', value: '50 per page' }, + { label: '100 records', value: '100 per page' }, + { label: '200 records', value: '200 per page' }, + { label: '500 records', value: '500 per page' }, + { label: '1000 records', value: '1000 per page' }, + ]; + + const RecordEnum = Object.freeze({ + '50 per page': 50, + '100 per page': 100, + '200 per page': 200, + '500 per page': 500, + '1000 per page': 1000, + }); + + const [selectedOption, setSelectedOption] = useState('50 per page'); + const [pageCount, setPageCount] = useState(1); + const [pageSize, setPageSize] = useState(RecordEnum[selectedOption]); + + const handleSelectChange = (value) => { + setSelectedOption(value); + setPageSize(RecordEnum[value]); + + setPageCount(1); + fetchTableData(`?limit=${RecordEnum[value]}&offset=0`, RecordEnum[value], 1); + }; + + const handlePageCountChange = (value) => { + setPageCount(value); + + const limit = RecordEnum[selectedOption]; + const offset = value === 1 ? 0 : (value - 1) * RecordEnum[selectedOption]; + + fetchTableData(`?limit=${limit}&offset=${offset}`, limit, value); + }; + + const gotoNextPage = (fromInput = false, value = null) => { + if (fromInput && value) { + return handlePageCountChange(value); + } + + setPageCount((prev) => { + return prev + 1; + }); + + const limit = RecordEnum[selectedOption]; + const offset = pageCount * RecordEnum[selectedOption]; + + fetchTableData(`?limit=${limit}&offset=${offset}`, limit, pageCount + 1); + }; + + const gotoPreviousPage = () => { + setPageCount((prev) => { + if (prev - 1 < 1) { + return prev; + } + return prev - 1; + }); + + const limit = RecordEnum[selectedOption]; + const offset = (pageCount - 2) * RecordEnum[selectedOption]; + + fetchTableData(`?limit=${limit}&offset=${offset}`, limit, pageCount - 1); + }; + + return ( +
+
+
+ +
+
+
+ +
+
+ { + if (event.key === 'Enter') { + handleOnChange(event.target.value); + } + }} + onChange={(event) => { + setCurrentPageNumber(event.target.value); + }} + /> + / {totalPage} +
+ + { + event.stopPropagation(); + gotoNextPage(); + }} + classNames={darkMode && 'dark'} + styles={{ height: '20px', width: '20px' }} + disabled={currentPage === totalPage} + > + + +
+ ); +}; + +export default Pagination; diff --git a/frontend/src/TooljetDatabase/Table/index.jsx b/frontend/src/TooljetDatabase/Table/index.jsx index 68dc4a62dd..e60e468ce4 100644 --- a/frontend/src/TooljetDatabase/Table/index.jsx +++ b/frontend/src/TooljetDatabase/Table/index.jsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-key */ import React, { useEffect, useState, useContext } from 'react'; import cx from 'classnames'; import { useTable, useRowSelect } from 'react-table'; @@ -11,14 +10,17 @@ import Skeleton from 'react-loading-skeleton'; import IndeterminateCheckbox from '@/_ui/IndeterminateCheckbox'; import Drawer from '@/_ui/Drawer'; import EditColumnForm from '../Forms/ColumnForm'; +import TableFooter from './Footer'; -const Table = () => { +const Table = ({ openCreateRowDrawer }) => { const { organizationId, columns, selectedTable, selectedTableData, setSelectedTableData, setColumns } = useContext(TooljetDatabaseContext); const [isEditColumnDrawerOpen, setIsEditColumnDrawerOpen] = useState(false); const [selectedColumn, setSelectedColumn] = useState(); const [loading, setLoading] = useState(false); + const [totalRecords, setTotalRecords] = useState(0); + const fetchTableMetadata = () => { tooljetDatabaseService.viewTable(organizationId, selectedTable).then(({ data = [], error }) => { if (error) { @@ -40,15 +42,18 @@ const Table = () => { }); }; - const fetchTableData = () => { + const fetchTableData = (queryParams = '', pagesize = 50, pagecount = 1) => { + const defaultQueryParams = `limit=${pagesize}&offset=${(pagecount - 1) * pagesize}`; + const params = queryParams ? queryParams : defaultQueryParams; setLoading(true); - tooljetDatabaseService.findOne(organizationId, selectedTable).then(({ data = [], error }) => { + tooljetDatabaseService.findOne(organizationId, selectedTable, params).then(({ headers, data = [], error }) => { setLoading(false); if (error) { toast.error(error?.message ?? `Error fetching table "${selectedTable}" data`); return; } - + const totalRecords = headers['content-range'].split('/')[1] || 0; + setTotalRecords(totalRecords); setSelectedTableData(data); }); }; @@ -58,6 +63,7 @@ const Table = () => { fetchTableData(); fetchTableMetadata(); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedTable]); const tableData = React.useMemo( @@ -214,9 +220,14 @@ const Table = () => { prepareRow(row); return ( - {row.cells.map((cell) => { + {row.cells.map((cell, index) => { return ( - + {isBoolean(cell?.value) ? cell?.value?.toString() : cell.render('Cell')} ); @@ -226,6 +237,12 @@ const Table = () => { })} +
setIsEditColumnDrawerOpen(false)} position="right"> { }; const darkMode = localStorage.getItem('darkMode') === 'true'; + const [isCreateRowDrawerOpen, setIsCreateRowDrawerOpen] = useState(false); return (
@@ -88,7 +89,10 @@ const TooljetDatabasePage = () => { <> - + )}
@@ -96,7 +100,7 @@ const TooljetDatabasePage = () => {
- +
setIsCreateRowDrawerOpen(true)} /> )} diff --git a/frontend/src/_styles/components.scss b/frontend/src/_styles/components.scss index 56ce3c2709..84f0934902 100644 --- a/frontend/src/_styles/components.scss +++ b/frontend/src/_styles/components.scss @@ -58,6 +58,14 @@ $btn-dark-color: #FFFFFF; line-height: 20px; } +.unstyled-button.dark { + color: #FFFFFF; + + img { + filter: brightness(0) invert(1); + } +} + .page-handle-button-container { border-radius: $base-border-radius; diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 6dd036c971..d122270b2b 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -8488,3 +8488,22 @@ tbody { // ONBOARDING-SELF-HOST STYLES END-------> //ONBOARD STYLES END---------------------------->>>>> +.toojet-db-table-footer { + position: fixed; + bottom: 0px; + width: -webkit-fill-available; +} + +.tooljet-db-pagination-container { + display: flex; + padding: 0px; + height: 20px; + + .form-control { + padding: 0 4px; + width: fit-content; + max-width: 30px; + text-align: center; + } + +} \ No newline at end of file diff --git a/frontend/src/_ui/Select/SelectComponent.jsx b/frontend/src/_ui/Select/SelectComponent.jsx index 3198737706..ab9c6d4c1b 100644 --- a/frontend/src/_ui/Select/SelectComponent.jsx +++ b/frontend/src/_ui/Select/SelectComponent.jsx @@ -17,6 +17,7 @@ export const SelectComponent = ({ options = [], value, onChange, ...restProps }) useMenuPortal = true, // todo: deperecate this prop, use menuPortalTarget instead maxMenuHeight = 250, menuPortalTarget = null, + menuPlacement = 'auto', } = restProps; const customStyles = defaultStyles(darkMode, width, height, styles); @@ -56,7 +57,7 @@ export const SelectComponent = ({ options = [], value, onChange, ...restProps }) placeholder={placeholder} styles={customStyles} formatOptionLabel={(option) => renderCustomOption(option)} - menuPlacement="auto" + menuPlacement={menuPlacement} maxMenuHeight={maxMenuHeight} menuPortalTarget={useMenuPortal ? document.body : menuPortalTarget} /> diff --git a/server/src/services/postgrest_proxy.service.ts b/server/src/services/postgrest_proxy.service.ts index 7cb3647333..5c9122005a 100644 --- a/server/src/services/postgrest_proxy.service.ts +++ b/server/src/services/postgrest_proxy.service.ts @@ -16,6 +16,9 @@ export class PostgrestProxyService { const authToken = 'Bearer ' + this.signJwtPayload(this.configService.get('PG_USER')); req.headers = {}; req.headers['Authorization'] = authToken; + req.headers['Prefer'] = 'count=exact'; // To get the total count of records + + res.set('Access-Control-Expose-Headers', 'Content-Range'); return this.httpProxy(req, res, next); }