From d41741514ab9f9987ae3341b697e92556f749094 Mon Sep 17 00:00:00 2001 From: Rajesh Jonnalagadda Date: Thu, 21 Mar 2024 12:13:05 +0530 Subject: [PATCH 1/3] fix:Added new tabs feature to separate the local data and added filter and sort functionality to local data too --- src/atoms/tableScope/rowActions.ts | 85 +++- src/atoms/tableScope/table.ts | 430 +++++++++++++++++- src/components/Table/Table.tsx | 1 + .../TableToolbar/Filters/Filters.tsx | 43 +- .../TableToolbar/LoadedRowsStatus.tsx | 18 +- src/pages/Table/ProvidedArraySubTablePage.tsx | 2 + src/pages/Table/ProvidedSubTablePage.tsx | 2 + src/pages/Table/ProvidedTablePage.tsx | 2 + src/pages/Table/TablePage.tsx | 161 ++++++- 9 files changed, 717 insertions(+), 27 deletions(-) diff --git a/src/atoms/tableScope/rowActions.ts b/src/atoms/tableScope/rowActions.ts index af5caddf8..20724214a 100644 --- a/src/atoms/tableScope/rowActions.ts +++ b/src/atoms/tableScope/rowActions.ts @@ -20,6 +20,9 @@ import { _updateRowDbAtom, _deleteRowDbAtom, _bulkWriteDbAtom, + tableSortsAtom, + tableRowsDbAtom, + tableTypeAtom, } from "./table"; import { @@ -66,10 +69,29 @@ export const addRowAtom = atom( if (!currentUser) throw new Error("Cannot read current user"); const auditChange = get(auditChangeAtom); const tableFilters = get(tableFiltersAtom); + const tableSort = get(tableSortsAtom); + const tableType = get(tableTypeAtom); + const tableColumnsOrdered = get(tableColumnsOrderedAtom); - const tableRows = get(tableRowsAtom); + const tableLocalRows = get(tableRowsLocalAtom); + const tableDbRows = get(tableRowsDbAtom); + let tableRows = get(tableRowsAtom); + //this will handle the out of order and id duplicate issues + if (tableType !== "old") { + tableRows = [...tableLocalRows, ...tableDbRows]; + } + + const updateTableType = () => { + if (tableType === "db") { + window.alert("Navigating to Play ground"); + set(tableTypeAtom, "local"); + } + }; - const _addSingleRowAndAudit = async (row: TableRow) => { + const _addSingleRowAndAudit = async ( + row: TableRow, + firstInOrderRowId?: string + ) => { // Store initial values to be written const initialValues: TableRow = { _rowy_ref: row._rowy_ref }; @@ -124,6 +146,8 @@ export const addRowAtom = atom( // - there are filters set and we couldn’t set the value of a field to // fit in the filtered query // - user did not set ID to decrement + + let updateInDb = false; if ( row._rowy_outOfOrder === true || outOfOrderFilters.size > 0 || @@ -133,20 +157,40 @@ export const addRowAtom = atom( type: "add", row: { ...rowValues, _rowy_outOfOrder: true }, }); + updateInDb = false; + updateTableType(); } // Also add to rowsLocal if any required fields are missing // (not out of order since those cases are handled above) - else if (missingRequiredFields.length > 0) { + + // - if adding the row at top of the out of order rows(firstInOrderRowId be zzzzzzz) and if there are no + // required columns in the table schema it will create data in db with path provided.(with id: zzzzzzy) + // and if again user tries to add row at top of out of order rows(firstInOrderRowId will again be zzzzzzz + // and next decrement id will always be firstInOrderRowId be zzzzzzy). it won't add row. so in this case + // pushing it into local rows to avoid this scenario . + else if ( + missingRequiredFields.length > 0 || + (requiredFields.length === 0 && !!firstInOrderRowId) + ) { set(tableRowsLocalAtom, { type: "add", row: { ...rowValues, _rowy_outOfOrder: false }, }); - } - - // Write to database if no required fields are missing + updateTableType(); + updateInDb = missingRequiredFields.length === 0; + } // Write to database if no required fields are missing else { + updateInDb = true; + } + if (updateInDb) { await updateRowDb(row._rowy_ref.path, omitRowyFields(rowValues)); + if (tableType === "local") { + window.alert( + "As the row added in the playground is updated to the db. so not considering as local row. redirecting it to db" + ); + set(tableTypeAtom, "db"); + } } if (auditChange) auditChange("ADD_ROW", row._rowy_ref.path); @@ -178,8 +222,22 @@ export const addRowAtom = atom( ? `${r._rowy_ref.path.split("/").slice(0, -1).join("/")}/${id}` : r._rowy_ref.path; + //if adding row which is already present in the local data. igonre it. do send a toast message + if ( + !setId && + tableLocalRows.find( + (lRow) => lRow._rowy_ref.path === r._rowy_ref.path + ) + ) { + window.alert("creating the row with already existing custom_id"); + continue; + } + promises.push( - _addSingleRowAndAudit(setId ? { ...r, _rowy_ref: { id, path } } : r) + _addSingleRowAndAudit( + setId ? { ...r, _rowy_ref: { id, path } } : r, + lastId + ) ); } @@ -195,9 +253,18 @@ export const addRowAtom = atom( const path = setId ? `${row._rowy_ref.path.split("/").slice(0, -1).join("/")}/${id}` : row._rowy_ref.path; - + if ( + !setId && + tableLocalRows.find( + (lRow) => lRow._rowy_ref.path === row._rowy_ref.path + ) + ) { + window.alert("creating the row with already existing custom_id"); + return; + } await _addSingleRowAndAudit( - setId ? { ...row, _rowy_ref: { id, path } } : row + setId ? { ...row, _rowy_ref: { id, path } } : row, + firstInOrderRowId ); } } diff --git a/src/atoms/tableScope/table.ts b/src/atoms/tableScope/table.ts index f1535a477..21b13d9e2 100644 --- a/src/atoms/tableScope/table.ts +++ b/src/atoms/tableScope/table.ts @@ -1,6 +1,27 @@ import { atom } from "jotai"; import { atomWithReducer, atomWithHash } from "jotai/utils"; -import { findIndex, cloneDeep, unset, orderBy } from "lodash-es"; +import { + findIndex, + cloneDeep, + unset, + orderBy, + isEmpty, + get as _get, + includes, + isNumber, + isArray, + intersection, + isObject, + size, +} from "lodash-es"; +import { + isAfter, + isBefore, + isMatch, + isSameDay, + parse, + isValid, +} from "date-fns"; import { TableSettings, @@ -20,6 +41,11 @@ import { Table } from "@tanstack/react-table"; /** Root atom from which others are derived */ export const tableIdAtom = atom(""); + +export const tableTypeAtom = atom<"db" | "local" | "old">("db"); + +export const canIncludeLocalDataAtom = atom(true); + /** Store tableSettings from project settings document */ export const tableSettingsAtom = atom({ id: "", @@ -167,31 +193,419 @@ export const tableRowsLocalAtom = atomWithReducer( /** Store rows from the db listener */ export const tableRowsDbAtom = atom([]); +export const possibleFormats = [ + "yyyy-MM-dd", + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd HH:mm", + "yyyy/MM/dd HH:mm:ss", + "yyyy/MM/dd HH:mm", + "yyyy/MM/dd", + "dd/MM/yyyy", + "MM/dd/yyyy", + "dd-MM-yyyy", + "MM-dd-yyyy", +]; + +const commonEvaluatorTypes = ["email", "phone", "markdown"]; + +const strictEvaluatorTypes = [ + "json", //costly operations on filters and sorts + "code", //costly operations on filters and sorts + "array", +]; + +const nummericEvaluatorType = [ + "percentage", + "number", + "rating", + "check_box", + "silder", +]; + +const dateEvalutorType = [ + "created_by", + "updated_by", + "created_at", + "updated_at", +]; + +function toArray(value: [] | string | null | undefined) { + return isArray(value) ? value : [value ?? ""]; +} + +function hasIntersected(filterValue: [] | string, rowValue: [] | string) { + if (!filterValue || !rowValue) { + return false; + } + return !isEmpty(intersection(toArray(filterValue), toArray(rowValue))); +} + +function stringifyAndCompare(filterValue: [] | string, rowValue: [] | string) { + return JSON.stringify(toArray(filterValue)).localeCompare( + JSON.stringify(toArray(rowValue)) + ); +} + +function strictCheck(filterValue: [] | string, rowValue: [] | string) { + try { + return stringifyAndCompare(filterValue, rowValue) === 0; + } catch (err) { + return false; + } +} + +export const parseDate = ( + dateString: string | null | undefined | Date, + format?: string +) => { + let tempDate = new Date(0); + if (!dateString) { + return tempDate; + } + if (typeof dateString !== "string") { + return isValid(dateString) ? dateString : new Date(0); + } + let parsedDate; + if (format && isMatch(dateString, format)) { + return parse(dateString, format, tempDate); + } + for (const tempFormat of possibleFormats) { + if (isMatch(dateString, tempFormat)) { + parsedDate = parse(dateString, tempFormat, tempDate); + break; + } + } + parsedDate = !parsedDate ? new Date(dateString) : parsedDate; + return parsedDate && isNaN(parsedDate.getTime()) ? tempDate : parsedDate; +}; + +export const EvaluteFilter = (filter: TableFilter, row: TableRow): boolean => { + let rowValue = _get(row, filter.key, ""); + let filterValue = filter.value ?? ""; + //handling the numbers with negative and 0 values; + if ( + filter.operator !== "is-empty" && + !isNumber(rowValue) && + (isEmpty(rowValue) || isEmpty(filterValue)) + ) { + return false; + } + try { + if (filter.operator.startsWith("date-")) { + rowValue = parseDate(rowValue, _get(filter, "format")); + filterValue = parseDate(filterValue, _get(filter, "format")); + } + switch (filter.operator) { + case "==": + return strictCheck(filterValue, rowValue); + case "array-contains": + case "array-contains-any": + return hasIntersected(filterValue, rowValue); + case "!=": + return !strictCheck(filterValue, rowValue); + // case "array-not-contains": return !hasIntersected(filterValue, rowValue); + case "is-empty": + return customEmptyCheck(rowValue) || !rowValue; + case "is-not-empty": + return !(customEmptyCheck(rowValue) || !rowValue); + case "date-equal": + return isSameDay(rowValue, filterValue); + case "date-before": + return isBefore(rowValue, filterValue); + case "date-after": + return isAfter(rowValue, filterValue); + case "date-before-equal": + return ( + isSameDay(rowValue, filterValue) || isBefore(rowValue, filterValue) + ); + case "date-after-equal": + return ( + isSameDay(rowValue, filterValue) || isAfter(rowValue, filterValue) + ); + // case "time-minute-equal": return new Date(rowValue).getTime() <= new Date(filterValue).getTime() ; + case "id-equal": + return filterValue && rowValue === filterValue; + } + } catch (err) {} + + return false; +}; + +export const isMetCriteria = ( + row: TableRow, + tableFilters: TableFilter[], + tableFiltersJoin: "AND" | "OR" +) => { + let flag = true; + for (const filter of tableFilters) { + if (tableFiltersJoin === "OR" && EvaluteFilter(filter, row)) { + return true; + } + if (tableFiltersJoin === "AND" && !EvaluteFilter(filter, row)) { + return false; + } + + flag = tableFiltersJoin === "OR" ? false : true; + } + return flag; +}; + +function getDateValue(date: any) { + if (!date) { + return new Date(0); + } + if (isValid(date)) { + return new Date(date); + } + if (!!date.toDate) { + return date.toDate(); + } + if (isNumber(date)) { + return new Date(date); + } + return new Date(0); +} + +function handleGeoData(location1: any, location2: any) { + if (!location1 && !location2) { + return 0; + } + if (!location1) { + return -1; + } + if (!location2) { + return 1; + } + try { + return location1._compareTo(location2); + } catch (err) { + console.error("error occured while comparing", err); + return -1; + } +} + +const customEmptyCheck = (value: any) => { + if (isNumber(value) || value instanceof Date || !!value?.toDate) { + return false; + } + return isObject(value) ? !size(value) : isEmpty(value); +}; + +function getDuration(durationMeta: Record) { + try { + const start = getDateValue(durationMeta.start); + const end = getDateValue(durationMeta.end); + return start - end; + } catch (err) { + return 0; + } +} + +export const customComparator = ( + a: TableRow, + b: TableRow, + sortFilters: TableSort[], + columnsMap: Record | undefined +) => { + let flag = 0; + for (const { key, direction } of sortFilters) { + let [aValue, bValue] = [a[key], b[key]]; + const columnMeta = _get(columnsMap, key); + + if (direction?.toLowerCase() === "desc") { + [aValue, bValue] = [bValue, aValue]; + } + if (aValue === bValue) { + continue; + } + const isAEmpty = customEmptyCheck(aValue); + const isBEmpty = customEmptyCheck(bValue); + if (isAEmpty && isBEmpty) continue; + if (isAEmpty) return -1; + if (isBEmpty) return 1; + + const fieldType = _get(columnMeta, "type", "").toLowerCase(); + if (!fieldType) { + continue; + } + switch (true) { + case fieldType.endsWith("text") || + includes(commonEvaluatorTypes, fieldType): + flag = aValue.localeCompare(bValue); + break; + + case includes(nummericEvaluatorType, fieldType): + flag = aValue - bValue; + break; + case fieldType.endsWith("_select"): + flag = stringifyAndCompare(aValue, bValue); + break; //as select option will have less number of options + case fieldType.startsWith("date") || + includes(dateEvalutorType, fieldType): + flag = getDateValue(aValue) - getDateValue(bValue); + break; + case fieldType === "duration": + flag = getDuration(aValue) - getDuration(bValue); + break; + case fieldType === "color": + flag = (aValue?.hex ?? "").localCompare(bValue?.hex ?? ""); + break; + case includes(strictEvaluatorTypes, fieldType): + stringifyAndCompare(aValue, bValue); + break; + case fieldType === "geo_point": + flag = handleGeoData(aValue, bValue); + } + if (flag !== 0) { + break; + } + } + return flag; +}; + +function mergeSortedArrays( + localRows: TableRow[], + dbRows: TableRow[], + tableSort: TableSort[], + columnsMap: Record | undefined +) { + const mergedArray = []; + let i = 0, + j = 0; + + while (i < localRows.length && j < dbRows.length) { + const comparatorValue = customComparator( + localRows[i], + dbRows[j], + tableSort, + columnsMap + ); + if (comparatorValue <= 0) { + mergedArray.push(localRows[i]); + i++; + } else { + mergedArray.push(dbRows[j]); + j++; + } + } + + while (i < localRows.length) { + mergedArray.push(localRows[i]); + i++; + } + + while (j < dbRows.length) { + mergedArray.push(dbRows[j]); + j++; + } + + return mergedArray; +} + +function handleFiltersAndSorts( + rows: TableRow[], + { + tableFilters, + tableSort, + tableFiltersJoin, + columnsMap, + }: { + tableFilters: TableFilter[]; + tableSort: TableSort[]; + tableFiltersJoin: "AND" | "OR"; + columnsMap?: Record | undefined; + } +) { + const isTableSortEmpty = isEmpty(tableSort); + const isTableFilterEmpty = isEmpty(tableFilters); + if (isTableSortEmpty && isTableFilterEmpty) { + return rows; + } + try { + rows = rows.filter((row) => { + //if filters are applied on the table. making sure that local rows were filtered. + if (!isTableFilterEmpty) { + return isMetCriteria(row, tableFilters, tableFiltersJoin); + } + return true; + }); + if (!isEmpty(tableSort) && !isEmpty(columnsMap)) { + rows = rows.sort((a, b) => customComparator(a, b, tableSort, columnsMap)); + } + } catch (err) { + console.error("Error occured while handling filters and sorts", err); + } finally { + return rows; + } +} + /** Combine tableRowsLocal and tableRowsDb */ export const tableRowsAtom = atom((get) => { + const tableType = get(tableTypeAtom); + let tableFilters = get(tableFiltersAtom); + let tableFiltersJoin = get(tableFiltersJoinAtom); + let tableSort = get(tableSortsAtom); const rowsDb = get(tableRowsDbAtom); - const rowsLocal = get(tableRowsLocalAtom); + if (tableType === "db") { + return [...rowsDb]; + } + + let rowsLocal = get(tableRowsLocalAtom); + const columnsMap = get(tableSchemaAtom)?.columns; // Optimization: create Map of rowsDb by path to index for faster lookup + const isTableSortEmpty = isEmpty(tableSort); + const isTableFilterEmpty = isEmpty(tableFilters); + const isTableColumnMapEmpty = isEmpty(columnsMap); + const rowsDbMap = new Map(); rowsDb.forEach((row, i) => rowsDbMap.set(row._rowy_ref.path, i)); - // Loop through rowsLocal, which is usually the smaller of the two arrays - const rowsLocalToMerge = rowsLocal.map((row, i) => { - // If row is in rowsDb, merge the two - // and remove from rowsDb to prevent duplication + + const canIncludeLocalData = get(canIncludeLocalDataAtom); + if (!canIncludeLocalData && tableType === "old") { + return [...rowsDb]; + } + + rowsLocal = rowsLocal.map((row) => { if (rowsDbMap.has(row._rowy_ref.path)) { const index = rowsDbMap.get(row._rowy_ref.path)!; const merged = updateRowData({ ...rowsDb[index] }, row); + //updating the last changes to local rows. + row = Object.assign(row, merged); rowsDbMap.delete(row._rowy_ref.path); return merged; } return row; }); - // Merge the two arrays + rowsLocal = handleFiltersAndSorts(rowsLocal, { + tableFilters, + tableSort, + tableFiltersJoin, + columnsMap, + }); + + if (tableType === "local") { + return [...rowsLocal]; + } + + // Merge the local sorted/filtered rows with db rows. + if ( + (!isTableSortEmpty || !isTableFilterEmpty) && + !isTableColumnMapEmpty && + !isEmpty(rowsLocal) + ) { + return mergeSortedArrays( + rowsLocal, + rowsDb.filter((row) => rowsDbMap.has(row._rowy_ref.path)), + tableSort, + columnsMap + ); + } + return [ - ...rowsLocalToMerge, + ...rowsLocal, ...rowsDb.filter((row) => rowsDbMap.has(row._rowy_ref.path)), ]; }); diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index ba17786ba..141da33f9 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -46,6 +46,7 @@ import useHotKeys from "./useHotKey"; import type { TableRow, ColumnConfig } from "@src/types/table"; import useStateWithRef from "./useStateWithRef"; // testing with useStateWithRef import { Checkbox, FormControlLabel } from "@mui/material"; +import { TableTypeComp } from "@src/pages/Table/TablePage"; export const DEFAULT_ROW_HEIGHT = 41; export const DEFAULT_COL_WIDTH = 150; diff --git a/src/components/TableToolbar/Filters/Filters.tsx b/src/components/TableToolbar/Filters/Filters.tsx index 2dbb450cd..d61790e95 100644 --- a/src/components/TableToolbar/Filters/Filters.tsx +++ b/src/components/TableToolbar/Filters/Filters.tsx @@ -36,6 +36,8 @@ import { updateTableSchemaAtom, tableFiltersPopoverAtom, tableFiltersJoinAtom, + tableTypeAtom, + canIncludeLocalDataAtom, } from "@src/atoms/tableScope"; import { useFilterInputs } from "./useFilterInputs"; import { analytics, logEvent } from "@src/analytics"; @@ -88,6 +90,7 @@ export default function Filters() { availableFiltersForEachSelectedColumn[0]; const setTableFiltersJoin = useSetAtom(tableFiltersJoinAtom, tableScope); + const [tableType] = useAtom(tableTypeAtom, tableScope); // Get table filters & user filters from config documents const tableFilters = useMemoValue( @@ -353,10 +356,14 @@ export default function Filters() { /> } label="Override table filters" - sx={{ justifyContent: "center", mb: 1, mr: 0 }} + sx={ + tableType === "old" + ? { justifyContent: "start", mb: 0, mr: 0 } + : { justifyContent: "center", mb: 1, mr: 0 } + } /> )} - + ); } + +export function CustomFormControllableForNewTabs() { + const [tableType] = useAtom(tableTypeAtom, tableScope); + const [canIncludeLocalData, setCanIncludeLocalData] = useAtom( + canIncludeLocalDataAtom, + tableScope + ); + return tableType === "old" ? ( + <> + { + setCanIncludeLocalData(e.target.checked); + }} + /> + } + label="Include the Local and out of order rows too." + sx={{ justifyContent: "start", mb: 1, mr: 0 }} + /> + + {/* +
    +
  • + The filter above will include the local rows and out of order +
  • +
+
*/} + + ) : null; +} diff --git a/src/components/TableToolbar/LoadedRowsStatus.tsx b/src/components/TableToolbar/LoadedRowsStatus.tsx index f216e80f1..e4024bf8e 100644 --- a/src/components/TableToolbar/LoadedRowsStatus.tsx +++ b/src/components/TableToolbar/LoadedRowsStatus.tsx @@ -1,5 +1,5 @@ -import { Suspense, forwardRef } from "react"; -import { useAtom } from "jotai"; +import { Suspense, forwardRef, useEffect } from "react"; +import { useAtom, useSetAtom } from "jotai"; import { Tooltip, Typography, TypographyProps } from "@mui/material"; import SyncIcon from "@mui/icons-material/Sync"; @@ -10,6 +10,7 @@ import { tableRowsAtom, tableNextPageAtom, serverDocCountAtom, + tableTypeAtom, } from "@src/atoms/tableScope"; import { spreadSx } from "@src/utils/ui"; import useOffline from "@src/hooks/useOffline"; @@ -60,8 +61,8 @@ function LoadedRowsStatus() { const [tableNextPage] = useAtom(tableNextPageAtom, tableScope); const [serverDocCount] = useAtom(serverDocCountAtom, tableScope); const [tableRows] = useAtom(tableRowsAtom, tableScope); - - if (tableNextPage.loading) + const [tableType] = useAtom(tableTypeAtom, tableScope); + if (tableNextPage.loading && tableType !== "local") return {loadingIcon}Loading more…; return ( @@ -70,8 +71,13 @@ function LoadedRowsStatus() { Loaded {!tableNextPage.available && "all "} {tableRows.length} - {serverDocCount !== undefined && ` of ${serverDocCount}`} row - {(serverDocCount ?? tableRows.length) !== 1 && "s"} + {tableType !== "local" && + serverDocCount !== undefined && + ` of ${serverDocCount}`}{" "} + row + {tableType !== "local" && + (serverDocCount ?? tableRows.length) !== 1 && + "s"} ); diff --git a/src/pages/Table/ProvidedArraySubTablePage.tsx b/src/pages/Table/ProvidedArraySubTablePage.tsx index ee3751544..8cc5bd46c 100644 --- a/src/pages/Table/ProvidedArraySubTablePage.tsx +++ b/src/pages/Table/ProvidedArraySubTablePage.tsx @@ -23,6 +23,7 @@ import { import { ROUTES } from "@src/constants/routes"; import { TOP_BAR_HEIGHT } from "@src/layouts/Navigation/TopBar"; import { TABLE_TOOLBAR_HEIGHT } from "@src/components/TableToolbar"; +import { TableTypeComp } from "./TablePage"; // prettier-ignore const TablePage = lazy(() => import("./TablePage" /* webpackChunkName: "TablePage" */)); @@ -148,6 +149,7 @@ export default function ProvidedArraySubTablePage() { "cloud_logs", ]} /> + diff --git a/src/pages/Table/ProvidedSubTablePage.tsx b/src/pages/Table/ProvidedSubTablePage.tsx index 649e9e907..45891031e 100644 --- a/src/pages/Table/ProvidedSubTablePage.tsx +++ b/src/pages/Table/ProvidedSubTablePage.tsx @@ -23,6 +23,7 @@ import { import { ROUTES } from "@src/constants/routes"; import { TOP_BAR_HEIGHT } from "@src/layouts/Navigation/TopBar"; import { TABLE_TOOLBAR_HEIGHT } from "@src/components/TableToolbar"; +import { TableTypeComp } from "./TablePage"; // prettier-ignore const TablePage = lazy(() => import("./TablePage" /* webpackChunkName: "TablePage" */)); @@ -138,6 +139,7 @@ export default function ProvidedSubTablePage() { + diff --git a/src/pages/Table/ProvidedTablePage.tsx b/src/pages/Table/ProvidedTablePage.tsx index 6adc676f3..57bf55680 100644 --- a/src/pages/Table/ProvidedTablePage.tsx +++ b/src/pages/Table/ProvidedTablePage.tsx @@ -31,6 +31,7 @@ import { import { SyncAtomValue } from "@src/atoms/utils"; import { ROUTES } from "@src/constants/routes"; import useDocumentTitle from "@src/hooks/useDocumentTitle"; +import { TableTypeComp } from "./TablePage"; // prettier-ignore const TablePage = lazy(() => import("./TablePage" /* webpackChunkName: "TablePage" */)); @@ -142,6 +143,7 @@ export default function ProvidedTablePage() { >
+
{outlet} diff --git a/src/pages/Table/TablePage.tsx b/src/pages/Table/TablePage.tsx index c92868212..8fc6dc7bb 100644 --- a/src/pages/Table/TablePage.tsx +++ b/src/pages/Table/TablePage.tsx @@ -3,7 +3,24 @@ import { useAtom } from "jotai"; import { ErrorBoundary } from "react-error-boundary"; import { isEmpty, intersection } from "lodash-es"; -import { Box, Fade } from "@mui/material"; +import { + Box, + Divider, + Fade, + IconButton, + ListItemText, + Menu, + MenuItem, + Tooltip, + TooltipProps, + Typography, + Zoom, + styled, + tooltipClasses, +} from "@mui/material"; +import MenuIcon from "@mui/icons-material/Menu"; +import NewReleasesIcon from "@mui/icons-material/NewReleases"; + import ErrorFallback, { InlineErrorFallback, } from "@src/components/ErrorFallback"; @@ -20,6 +37,8 @@ import TableModals from "@src/components/TableModals"; import EmptyState from "@src/components/EmptyState"; import AddRow from "@src/components/TableToolbar/AddRow"; import { AddRow as AddRowIcon } from "@src/assets/icons"; +import ToggleButton from "@mui/material/ToggleButton"; +import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; import { projectScope, @@ -33,6 +52,7 @@ import { tableSchemaAtom, columnModalAtom, tableModalAtom, + tableTypeAtom, } from "@src/atoms/tableScope"; import useBeforeUnload from "@src/hooks/useBeforeUnload"; import ActionParamsProvider from "@src/components/fields/Action/FormDialog/Provider"; @@ -43,6 +63,7 @@ import { DRAWER_COLLAPSED_WIDTH } from "@src/components/SideDrawer"; import { formatSubTableName } from "@src/utils/table"; import { TableToolsType } from "@src/types/table"; import { RowSelectionState } from "@tanstack/react-table"; +import React from "react"; // prettier-ignore const BuildLogsSnack = lazy(() => import("@src/components/TableModals/CloudLogsModal/BuildLogs/BuildLogsSnack" /* webpackChunkName: "TableModals-BuildLogsSnack" */)); @@ -162,7 +183,7 @@ export default function TablePage({ }> ); } + +export function TableTypeComp() { + const [tableType, setTableType] = useAtom(tableTypeAtom, tableScope); + const [anchorEl, setAnchorEl] = useState(null); + const [tableId] = useAtom(tableIdAtom, tableScope); + const handleClick = (event: any) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = (type: "db" | "local" | "old") => { + setAnchorEl(null); + if (type) { + setTableType(type); + } + }; + const handleChange = ( + event: React.MouseEvent, + type: "db" | "local" | "old" + ) => { + event.preventDefault(); + if (type !== null) { + setTableType(type); + } + }; + + const HtmlTooltip = styled(({ className, ...props }: TooltipProps) => ( + + ))(({ theme }) => ({ + [`& .${tooltipClasses.tooltip}`]: { + backgroundColor: "#f5f5f9", + color: "rgba(0, 0, 0, 0.87)", + maxWidth: 350, + fontSize: theme.typography.pxToRem(12), + border: "1px solid #dadde9", + marginLeft: "5px", + }, + })); + + return !isEmpty(tableId) ? ( +
+
+ + + + + handleClose("db")}> + DB + + + handleClose("local")}> + Local Playground + + + handleClose("old")}> + Old View + + +
+ + + DB + + + Local Playground + + + Old View + + + + + Tabs Overview + + + Click on each tab to switch between different views: + +
    +
  • + Db: View data from the database. +
  • +
  • + Local: View data in the local playground. +
  • +
  • + Old View: Switch to the old view. +
  • +
+ note: Filters and sorts issue were taken care! + + } + > + + + {/* */} + +
+
+ ) : null; +} From 068cfe82dda8c7783d44d41f8e10d65b72c3eac3 Mon Sep 17 00:00:00 2001 From: Rajesh Jonnalagadda Date: Thu, 21 Mar 2024 15:59:26 +0530 Subject: [PATCH 2/3] fix:fixed the page auto reloadingand scrolling issue while adding a row when sort and filter applied --- src/atoms/tableScope/table.ts | 48 ++++++++++++---- .../TableToolbar/Filters/Filters.tsx | 56 ++++++++----------- 2 files changed, 61 insertions(+), 43 deletions(-) diff --git a/src/atoms/tableScope/table.ts b/src/atoms/tableScope/table.ts index 21b13d9e2..b9232fb7a 100644 --- a/src/atoms/tableScope/table.ts +++ b/src/atoms/tableScope/table.ts @@ -219,7 +219,7 @@ const nummericEvaluatorType = [ "number", "rating", "check_box", - "silder", + "slider", ]; const dateEvalutorType = [ @@ -401,6 +401,14 @@ function getDuration(durationMeta: Record) { } } +function parseIntoNumber(value: string | number | undefined | null) { + value = Number(value); + if (isNumber(value) && isNaN(value)) { + return 0; + } + return value; +} + export const customComparator = ( a: TableRow, b: TableRow, @@ -409,12 +417,16 @@ export const customComparator = ( ) => { let flag = 0; for (const { key, direction } of sortFilters) { - let [aValue, bValue] = [a[key], b[key]]; + let [aValue, bValue] = [_get(a, key), _get(b, key)]; const columnMeta = _get(columnsMap, key); - if (direction?.toLowerCase() === "desc") { [aValue, bValue] = [bValue, aValue]; } + const fieldType = _get(columnMeta, "type", "").toLowerCase(); + if (fieldType === "check_box") { + [aValue, bValue] = [Boolean(aValue), Boolean(bValue)]; + return aValue - bValue; + } if (aValue === bValue) { continue; } @@ -424,7 +436,6 @@ export const customComparator = ( if (isAEmpty) return -1; if (isBEmpty) return 1; - const fieldType = _get(columnMeta, "type", "").toLowerCase(); if (!fieldType) { continue; } @@ -435,6 +446,7 @@ export const customComparator = ( break; case includes(nummericEvaluatorType, fieldType): + [aValue, bValue] = [parseIntoNumber(aValue), parseIntoNumber(bValue)]; flag = aValue - bValue; break; case fieldType.endsWith("_select"): @@ -467,20 +479,32 @@ function mergeSortedArrays( localRows: TableRow[], dbRows: TableRow[], tableSort: TableSort[], - columnsMap: Record | undefined + columnsMap: Record | undefined, + pageInfo: number, + nextPageInfo: NextPageState ) { const mergedArray = []; let i = 0, j = 0; - while (i < localRows.length && j < dbRows.length) { + console.log("nextPageInfo", nextPageInfo); + console.log("pageInfo", pageInfo); + const currentLoadedData = (pageInfo + 1) * 30; + while ( + i < localRows.length && + j < dbRows.length && + mergedArray.length < currentLoadedData + ) { const comparatorValue = customComparator( localRows[i], dbRows[j], tableSort, columnsMap ); - if (comparatorValue <= 0) { + if ( + comparatorValue <= 0 && + (mergedArray.length < currentLoadedData || !nextPageInfo.available) + ) { mergedArray.push(localRows[i]); i++; } else { @@ -489,12 +513,12 @@ function mergeSortedArrays( } } - while (i < localRows.length) { + while (i < localRows.length && mergedArray.length < currentLoadedData) { mergedArray.push(localRows[i]); i++; } - while (j < dbRows.length) { + while (j < dbRows.length && mergedArray.length < currentLoadedData) { mergedArray.push(dbRows[j]); j++; } @@ -546,6 +570,8 @@ export const tableRowsAtom = atom((get) => { let tableFiltersJoin = get(tableFiltersJoinAtom); let tableSort = get(tableSortsAtom); const rowsDb = get(tableRowsDbAtom); + const pageInfo = get(tablePageAtom); + const nextPageInfo = get(tableNextPageAtom); if (tableType === "db") { return [...rowsDb]; } @@ -600,7 +626,9 @@ export const tableRowsAtom = atom((get) => { rowsLocal, rowsDb.filter((row) => rowsDbMap.has(row._rowy_ref.path)), tableSort, - columnsMap + columnsMap, + pageInfo, + nextPageInfo ); } diff --git a/src/components/TableToolbar/Filters/Filters.tsx b/src/components/TableToolbar/Filters/Filters.tsx index d61790e95..93c2ac72f 100644 --- a/src/components/TableToolbar/Filters/Filters.tsx +++ b/src/components/TableToolbar/Filters/Filters.tsx @@ -192,6 +192,11 @@ export default function Filters() { const [overrideTableFilters, setOverrideTableFilters] = useState( tableFiltersOverridden ); + const [localDataCheck, setLocalDataCheck] = useState(true); + const setCanIncludeLocalData = useSetAtom( + canIncludeLocalDataAtom, + tableScope + ); useEffect(() => { if (userSettings.tables?.[tableId]?.joinOperator) { @@ -363,7 +368,23 @@ export default function Filters() { } /> )} - + {tableType === "old" ? ( + <> + { + setLocalDataCheck(e.target.checked); + }} + /> + } + label="Include the Local and out of order rows too." + sx={{ justifyContent: "start", mb: 1, mr: 0 }} + /> + + ) : null} + {/* */} @@ -587,35 +609,3 @@ export default function Filters() { ); } - -export function CustomFormControllableForNewTabs() { - const [tableType] = useAtom(tableTypeAtom, tableScope); - const [canIncludeLocalData, setCanIncludeLocalData] = useAtom( - canIncludeLocalDataAtom, - tableScope - ); - return tableType === "old" ? ( - <> - { - setCanIncludeLocalData(e.target.checked); - }} - /> - } - label="Include the Local and out of order rows too." - sx={{ justifyContent: "start", mb: 1, mr: 0 }} - /> - - {/* -
    -
  • - The filter above will include the local rows and out of order -
  • -
-
*/} - - ) : null; -} From a11159a50358e28c58afcd6e04ecf8700e9cce42 Mon Sep 17 00:00:00 2001 From: Rajesh Jonnalagadda Date: Thu, 21 Mar 2024 16:33:26 +0530 Subject: [PATCH 3/3] chore:removed window alerts --- src/atoms/tableScope/rowActions.ts | 9 ++------- src/atoms/tableScope/table.ts | 2 +- src/components/TableToolbar/Filters/Filters.tsx | 1 - 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/atoms/tableScope/rowActions.ts b/src/atoms/tableScope/rowActions.ts index 20724214a..11e576302 100644 --- a/src/atoms/tableScope/rowActions.ts +++ b/src/atoms/tableScope/rowActions.ts @@ -23,6 +23,7 @@ import { tableSortsAtom, tableRowsDbAtom, tableTypeAtom, + tablePageAtom, } from "./table"; import { @@ -82,8 +83,7 @@ export const addRowAtom = atom( } const updateTableType = () => { - if (tableType === "db") { - window.alert("Navigating to Play ground"); + if (tableType === "db" || (tableType === "old" && tableSort.length)) { set(tableTypeAtom, "local"); } }; @@ -186,9 +186,6 @@ export const addRowAtom = atom( if (updateInDb) { await updateRowDb(row._rowy_ref.path, omitRowyFields(rowValues)); if (tableType === "local") { - window.alert( - "As the row added in the playground is updated to the db. so not considering as local row. redirecting it to db" - ); set(tableTypeAtom, "db"); } } @@ -229,7 +226,6 @@ export const addRowAtom = atom( (lRow) => lRow._rowy_ref.path === r._rowy_ref.path ) ) { - window.alert("creating the row with already existing custom_id"); continue; } @@ -259,7 +255,6 @@ export const addRowAtom = atom( (lRow) => lRow._rowy_ref.path === row._rowy_ref.path ) ) { - window.alert("creating the row with already existing custom_id"); return; } await _addSingleRowAndAudit( diff --git a/src/atoms/tableScope/table.ts b/src/atoms/tableScope/table.ts index b9232fb7a..7f3171b3d 100644 --- a/src/atoms/tableScope/table.ts +++ b/src/atoms/tableScope/table.ts @@ -42,7 +42,7 @@ import { Table } from "@tanstack/react-table"; /** Root atom from which others are derived */ export const tableIdAtom = atom(""); -export const tableTypeAtom = atom<"db" | "local" | "old">("db"); +export const tableTypeAtom = atom<"db" | "local" | "old">("old"); export const canIncludeLocalDataAtom = atom(true); diff --git a/src/components/TableToolbar/Filters/Filters.tsx b/src/components/TableToolbar/Filters/Filters.tsx index 93c2ac72f..013aefa7f 100644 --- a/src/components/TableToolbar/Filters/Filters.tsx +++ b/src/components/TableToolbar/Filters/Filters.tsx @@ -384,7 +384,6 @@ export default function Filters() { /> ) : null} - {/* */}