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 (
+
+
+
+
+
+
+
+
+
+
+
+ {pageCount}-{pageSize} of {totalRecords} Records
+
+
+
+
+
+ );
+};
+
+export default Footer;
diff --git a/frontend/src/TooljetDatabase/Table/Paginations.jsx b/frontend/src/TooljetDatabase/Table/Paginations.jsx
new file mode 100644
index 0000000000..56bc6b0708
--- /dev/null
+++ b/frontend/src/TooljetDatabase/Table/Paginations.jsx
@@ -0,0 +1,65 @@
+import React, { useEffect } from 'react';
+import { Button } from '@/_ui/LeftSidebar';
+
+const Pagination = ({ darkMode, gotoNextPage, gotoPreviousPage, currentPage, totalPage }) => {
+ const [currentPageNumber, setCurrentPageNumber] = React.useState(currentPage);
+
+ const handleOnChange = (value) => {
+ const parsedValue = parseInt(value, 10);
+
+ if (parsedValue > 0 && parsedValue <= totalPage && parsedValue !== currentPage) {
+ gotoNextPage(true, event.target.value);
+ }
+ };
+
+ useEffect(() => {
+ setCurrentPageNumber(currentPage);
+ }, [currentPage]);
+
+ return (
+
+
{
+ event.stopPropagation();
+ gotoPreviousPage();
+ }}
+ classNames={darkMode ? 'dark' : 'nothing'}
+ styles={{ height: '20px', width: '20px' }}
+ disabled={currentPage === 1}
+ >
+
+
+
+
+ {
+ 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);
}