diff --git a/Common/Models/DatabaseModels/TableView.ts b/Common/Models/DatabaseModels/TableView.ts index f60b29cb80..4618d8468b 100644 --- a/Common/Models/DatabaseModels/TableView.ts +++ b/Common/Models/DatabaseModels/TableView.ts @@ -380,7 +380,7 @@ export default class TableView extends BaseModel { unique: false, nullable: false, }) - public filters?: Query = undefined; + public query?: Query = undefined; @ColumnAccessControl({ create: [ diff --git a/Common/UI/Components/ModelTable/TableView.tsx b/Common/UI/Components/ModelTable/TableView.tsx new file mode 100644 index 0000000000..3f5137581d --- /dev/null +++ b/Common/UI/Components/ModelTable/TableView.tsx @@ -0,0 +1,294 @@ +import React, { + FunctionComponent, + ReactElement, + useEffect, + useState, +} from "react"; +import TableView from "../../../Models/DatabaseModels/TableView"; +import ObjectID from "../../../Types/ObjectID"; +import MoreMenu from "../MoreMenu/MoreMenu"; +import MoreMenuItem from "../MoreMenu/MoreMenuItem"; +import ListResult from "../../Utils/BaseDatabase/ListResult"; +import ModelAPI from "../../Utils/ModelAPI/ModelAPI"; +import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax"; +import SortOrder from "../../../Types/BaseDatabase/SortOrder"; +import API from "../../../Utils/API"; +import MoreMenuSection from "../MoreMenu/MoreMenuSection"; +import Button, { ButtonStyleType } from "../Button/Button"; +import IconProp from "../../../Types/Icon/IconProp"; +import { BarLoader } from "react-spinners"; +import ConfirmModal from "../Modal/ConfirmModal"; +import ModelFormModal from "../ModelFormModal/ModelFormModal"; +import { FormType } from "../Forms/ModelForm"; +import FormFieldSchemaType from "../Forms/Types/FormFieldSchemaType"; +import { PromiseVoidFunction } from "../../../Types/FunctionTypes"; +import { GetReactElementFunction } from "../../Types/FunctionTypes"; + +export interface ComponentProps { + tableId: string; + onViewChange: (tableView: TableView | null) => void; + currentTableView: TableView | null; + projectId: ObjectID; + userId: ObjectID; +} + +const TableViewElement: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + const [error, setError] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [allTableViews, setAllTableViews] = useState>([]); + const [tableViewToDelete, setTableViewToDelete] = useState< + TableView | undefined + >(undefined); + const [tableViewToEdit, setTableViewToEdit] = useState( + undefined, + ); + const [showCreateNewViewModal, setShowCreateNewViewModel] = + useState(false); + + // load all the filters for this user and for this project. + const fetchTableViews: PromiseVoidFunction = async (): Promise => { + try { + setError(""); + setIsLoading(true); + + const tableViews: ListResult = await ModelAPI.getList({ + modelType: TableView, + query: { + projectId: props.projectId, + createdByUserId: props.userId, + tableId: props.tableId, + }, + limit: LIMIT_PER_PROJECT, + skip: 0, + select: { + sort: true, + itemsOnPage: true, + query: true, + name: true, + }, + sort: { + name: SortOrder.Ascending, + }, + }); + + setAllTableViews(tableViews.data); + } catch (err) { + setError(API.getFriendlyErrorMessage(err as Error)); + } + + setIsLoading(false); + }; + + type DeleteTableViewFunction = (tableView: TableView) => Promise; + + const deleteTableView: DeleteTableViewFunction = async ( + tableView: TableView, + ): Promise => { + const tableViewId: ObjectID = tableView.id!; + + try { + setError(""); + setIsLoading(true); + + await ModelAPI.deleteItem({ + modelType: TableView, + id: tableViewId, + }); + + await fetchTableViews(); + } catch (err) { + setError(API.getFriendlyErrorMessage(err as Error)); + } + + setIsLoading(false); + }; + + useEffect(() => { + fetchTableViews().catch((err: Error) => { + setError(API.getFriendlyErrorMessage(err as Error)); + }); + }, []); + + type GetRightElementForTableViewMenuItemFunction = ( + item: TableView, + ) => React.JSX.Element; + + const getRightElementForTableViewMenuItem: GetRightElementForTableViewMenuItemFunction = + (item: TableView): ReactElement => { + return ( +
+
+ ); + }; + + type GetViewItemsFunction = () => Array; + + const getViewItems: GetViewItemsFunction = (): Array => { + return allTableViews.map((item: TableView, index: number) => { + return ( + { + props.onViewChange && props.onViewChange(item); + }} + /> + ); + }); + }; + + const getMenuContents: GetReactElementFunction = (): ReactElement => { + if (isLoading) { + return ; + } + + return ( + <> + {allTableViews.length > 0 ? ( + + {getViewItems()} + + ) : ( + <> + )} + {}}> + + ); + }; + + if (error) { + return ( + { + return setError(""); + }} + /> + ); + } + + if (tableViewToEdit) { + return ( + + modelType={TableView} + modelIdToEdit={tableViewToEdit.id!} + name="Edit View" + title="Edit View" + description="You can rename this table view to any name you like." + onClose={() => { + setTableViewToEdit(undefined); + }} + submitButtonText="Save Changes" + onSuccess={async () => { + setTableViewToEdit(undefined); + await fetchTableViews(); + }} + formProps={{ + name: "Edit View", + modelType: TableView, + id: "edit-table-view", + fields: [ + { + field: { + name: true, + }, + fieldType: FormFieldSchemaType.Text, + placeholder: "Name of the view", + description: "Please enter the new name of the view", + title: "Name", + required: true, + }, + ], + formType: FormType.Update, + }} + /> + ); + } + + if (tableViewToDelete) { + return ( + { + await deleteTableView(tableViewToDelete); + setTableViewToDelete(undefined); + }} + onClose={() => { + setTableViewToDelete(undefined); + }} + submitButtonText={`Delete`} + submitButtonType={ButtonStyleType.DANGER} + /> + ); + } + + if (showCreateNewViewModal) { + return ( + + modelType={TableView} + name="Save New View" + title="Save New View" + description="You can save the current table settings as a new view." + onClose={() => { + setShowCreateNewViewModel(false); + }} + submitButtonText="Save Changes" + onBeforeCreate={(tableView: TableView) => { + tableView.query = props.currentTableView?.query || {}; + tableView.itemsOnPage = props.currentTableView?.itemsOnPage || 10; + tableView.sort = props.currentTableView?.sort || {}; + return Promise.resolve(tableView); + }} + onSuccess={async () => { + setShowCreateNewViewModel(false); + await fetchTableViews(); + }} + formProps={{ + name: "Save New View", + modelType: TableView, + id: "save-table-view", + fields: [ + { + field: { + name: true, + }, + fieldType: FormFieldSchemaType.Text, + placeholder: "Name of the view", + description: "Please enter the new name of the view", + title: "Name", + required: true, + }, + ], + formType: FormType.Create, + }} + /> + ); + } + + return {getMenuContents()}; +}; + +export default TableViewElement; diff --git a/Common/UI/Components/MoreMenu/Divider.tsx b/Common/UI/Components/MoreMenu/Divider.tsx index fc884f5540..853b08a97c 100644 --- a/Common/UI/Components/MoreMenu/Divider.tsx +++ b/Common/UI/Components/MoreMenu/Divider.tsx @@ -1,7 +1,7 @@ import React, { FunctionComponent, ReactElement } from "react"; -const MoreMenuItem: FunctionComponent = (): ReactElement => { +const MoreMenuDivider: FunctionComponent = (): ReactElement => { return
; }; -export default MoreMenuItem; +export default MoreMenuDivider; diff --git a/Common/UI/Components/MoreMenu/MoreMenuItem.tsx b/Common/UI/Components/MoreMenu/MoreMenuItem.tsx index 6ea07caa6c..fea7172a60 100644 --- a/Common/UI/Components/MoreMenu/MoreMenuItem.tsx +++ b/Common/UI/Components/MoreMenu/MoreMenuItem.tsx @@ -6,6 +6,7 @@ export interface ComponentProps { icon?: IconProp | undefined; text: string; onClick: () => void; + rightElement?: Array | ReactElement | undefined; } const MoreMenuItem: FunctionComponent = ( @@ -25,7 +26,10 @@ const MoreMenuItem: FunctionComponent = ( className="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500" /> )} - {props.text} +
+
{props.text}
+
{props.rightElement}
+
); }; diff --git a/Common/UI/Components/MoreMenu/MoreMenuSection.tsx b/Common/UI/Components/MoreMenu/MoreMenuSection.tsx new file mode 100644 index 0000000000..4f0d4612a8 --- /dev/null +++ b/Common/UI/Components/MoreMenu/MoreMenuSection.tsx @@ -0,0 +1,22 @@ +import React, { FunctionComponent, ReactElement } from "react"; +import MoreMenuDivider from "./Divider"; +export interface ComponentProps { + title: string; + children: Array | ReactElement; +} + +const MoreMenuSection: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + return ( +
+
+ {props.title.toLocaleUpperCase()} +
+ {props.children} + +
+ ); +}; + +export default MoreMenuSection;