From c1071efcd063444db4cd197a753fec7a8d159705 Mon Sep 17 00:00:00 2001 From: Yasiu1376 Date: Mon, 10 Jul 2023 14:41:13 +0330 Subject: [PATCH] implement base of Datatable_components --- public/locales/fa/app.json | 7 +- src/core/components/Datatable.jsx | 173 ++++++++++++++++++ src/core/components/DatatableStructure.jsx | 15 ++ src/lib/app/contexts/DatatableContext.jsx | 14 ++ .../app/contexts/ReloadDatatableContext.jsx | 15 ++ src/lib/app/hooks/useDatatable.jsx | 169 +++++++++++++++++ 6 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 src/core/components/Datatable.jsx create mode 100644 src/core/components/DatatableStructure.jsx create mode 100644 src/lib/app/contexts/DatatableContext.jsx create mode 100644 src/lib/app/contexts/ReloadDatatableContext.jsx create mode 100644 src/lib/app/hooks/useDatatable.jsx diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index 7d0f4ed..89aa688 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -17,7 +17,12 @@ "title_login_expert_page": "ورود کارشناس" }, "sidebar": { - "dashboard": "داشبورد" + "dashboard": "داشبورد", + "passenger-office": "اداره مسافر", + "machinary-office": "ماشین آلات", + "passenger-boss": "رئیس مسافر", + "transportation-assistance": "معاونت حمل و نقل", + "master": "مدیر کل" }, "Authorization": { "typography_your_login_is_valid_and_you_do_not_need_to_login_again": "شما دسترسی لازم به این صفحه را دارید نیاز به ورود مجدد نیست.", diff --git a/src/core/components/Datatable.jsx b/src/core/components/Datatable.jsx new file mode 100644 index 0000000..149c543 --- /dev/null +++ b/src/core/components/Datatable.jsx @@ -0,0 +1,173 @@ +import { ReloadDataTableContext } from "@/lib/app/contexts/ReloadDatatableContext"; +import useLanguage from "@/lib/app/hooks/useLanguage"; +import { Typography } from "@mui/material"; +import axios from "axios"; +import MaterialReactTable from "material-react-table"; +import moment from "moment"; +import { useTranslations } from "next-intl"; +import { useContext, useEffect, useMemo, useState } from "react"; +import useSWR from "swr"; + +const DataTable = (props) => { + const { reloadDataTable, setReloadDataTable } = useContext( + ReloadDataTableContext + ); + const fetcher = (...args) => { + return axios + .get(args, { + headers: { + Authorization: `Bearer ${props.token}`, + }, + }) + .then((res) => { + setRowCount(res.data.meta.totalRowCount); + return res.data.data; + }); + }; + const t = useTranslations(); + const { languageApp, languageList } = useLanguage(); + const [columnFilters, setColumnFilters] = useState([]); + const [columnFilterFns, setColumnFilterFns] = useState(() => { + let output = {}; + const list = props.columns.map((item) => + item.enableColumnFilter ? { [item.id]: item.filterFn } : { [item.id]: "" } + ); + for (var key in list) { + var nestedObj = list[key]; + for (var nestedKey in nestedObj) { + output[nestedKey] = nestedObj[nestedKey]; + } + } + return output; + }); + const [sorting, setSorting] = useState([]); + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 }); + const [rowCount, setRowCount] = useState(0); + const [updateTime, setupdateTime] = useState( + moment().locale(languageApp).format("LL (HH:mm:ss)") + ); + + const tableLocalization = useMemo( + () => + languageList.find((item) => item.key == languageApp).tableLocalization, + [languageApp, languageList] + ); + + const fetchUrl = useMemo(() => { + const url = new URL(props.tableUrl /* send your api */); + url.searchParams.set( + "start", + `${pagination.pageIndex * pagination.pageSize}` + ); + const filters = columnFilters.map((filter) => { + let datatype; + for (const i in props.columns) { + if (props.columns[i].id == filter.id) { + datatype = props.columns[i].datatype; + } + } + return { + ...filter, + fn: columnFilterFns[filter.id], + datatype: datatype, + }; + }); + url.searchParams.set("size", pagination.pageSize); + url.searchParams.set("filters", JSON.stringify(filters ?? [])); + url.searchParams.set("sorting", JSON.stringify(sorting ?? [])); + return url; + }, [ + props.tableUrl, + columnFilters, + columnFilterFns, + pagination, + sorting, + props.columns, + ]); + const { data, isLoading, isValidating, mutate } = useSWR(fetchUrl, fetcher, { + revalidateOnFocus: false, + }); + + useEffect(() => { + if (reloadDataTable) { + mutate(fetchUrl).then(() => { + setReloadDataTable(false); // Set reloadDataTable to false after mutation is complete + }); + } + }, [reloadDataTable, setReloadDataTable]); + + useEffect(() => { + setupdateTime(moment().locale(languageApp).format("LL (HH:mm:ss)")); + }, [isValidating, languageApp]); + + return ( + ( + <> + {props.enableCustomToolbar /* send condition */ + ? props.CustomToolbar /* send component */ + : ""} + + )} + renderBottomToolbarCustomActions={({ table }) => ( + <> + {props.enableLastUpdate /* send condition */ ? ( + + {t("last_updated_at")}: {updateTime} + + ) : ( + "" + )} + + )} + state={{ + isLoading, + columnFilters, + columnFilterFns, + pagination, + sorting, + }} + positionActionsColumn={"last"} + enableRowActions={props.enableRowActions} + {...props} + /> + ); +}; + +export default DataTable; diff --git a/src/core/components/DatatableStructure.jsx b/src/core/components/DatatableStructure.jsx new file mode 100644 index 0000000..e13ed16 --- /dev/null +++ b/src/core/components/DatatableStructure.jsx @@ -0,0 +1,15 @@ +import { DataTableProvider } from "@/lib/app/contexts/DatatableContext"; +import DataTable from "./Datatable"; +import { ReloadDataTableProvider } from "@/lib/app/contexts/ReloadDatatableContext"; + +function DataTableStructure(props) { + return ( + + + + + + ); +} + +export default DataTableStructure; diff --git a/src/lib/app/contexts/DatatableContext.jsx b/src/lib/app/contexts/DatatableContext.jsx new file mode 100644 index 0000000..d751640 --- /dev/null +++ b/src/lib/app/contexts/DatatableContext.jsx @@ -0,0 +1,14 @@ +import { createContext } from "react"; +import useDataTable from "../hooks/useDataTable"; + +export const DataTableContext = createContext(); + +export const DataTableProvider = (props) => { + const contextValue = useDataTable(); + + return ( + + {props.children} + + ); +}; diff --git a/src/lib/app/contexts/ReloadDatatableContext.jsx b/src/lib/app/contexts/ReloadDatatableContext.jsx new file mode 100644 index 0000000..05c788b --- /dev/null +++ b/src/lib/app/contexts/ReloadDatatableContext.jsx @@ -0,0 +1,15 @@ +import { createContext, useState } from "react"; + +export const ReloadDataTableContext = createContext(); + +export const ReloadDataTableProvider = ({ children }) => { + const [reloadDataTable, setReloadDataTable] = useState(false); + + return ( + + {children} + + ); +}; diff --git a/src/lib/app/hooks/useDatatable.jsx b/src/lib/app/hooks/useDatatable.jsx new file mode 100644 index 0000000..e0626e7 --- /dev/null +++ b/src/lib/app/hooks/useDatatable.jsx @@ -0,0 +1,169 @@ +import { useMediaQuery } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import { useState, useReducer, useCallback, useContext } from "react"; +import axios from "axios"; +import ImageResizer from "@/core/components/ImageConvertor"; +import useUser from "./useUser"; +import { ReloadDataTableContext } from "../contexts/ReloadDatatableContext"; + +const initialValues = { + url: "", + rowID: "", + values: {}, + image: { + key: "", + value: {}, + }, +}; + +const reducer = (state, action) => { + switch (action.type) { + case "ADD_DATA": + return { + ...state, + url: action.url, + values: action.values, + image: action.image, + rowID: "", + }; + case "EDIT_DATA": + return { + ...state, + url: action.url, + values: action.values, + rowID: action.rowID, + image: action.image, + }; + case "DELETE_DATA": + return { + ...state, + url: action.url, + rowID: action.rowID, + values: {}, + image: {}, + }; + default: + return state; + } +}; + +const useDataTable = () => { + const { setReloadDataTable } = useContext(ReloadDataTableContext); + const [state, dispatch] = useReducer(reducer, initialValues); + const [drawerContent, setDrawerContent] = useState(null); + const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + const [rowId, setRowId] = useState(null); + const theme = useTheme(); + const { token } = useUser(); + + const drawerDirection = useMediaQuery(theme.breakpoints.up("sm")) + ? "right" + : "bottom"; + const [sweepableDrawer, setSweepableDrawer] = useState({ + bottom: false, + right: false, + }); + + const addData = useCallback(async (url, values, image) => { + dispatch({ type: "ADD_DATA", url: url, values: values, image: image }); + if (image != null) { + var resizedAvatar = await ImageResizer(image.value); + formData.append(image.key, resizedAvatar); + axios.post(url, formData, { + headers: { + authorization: `Bearer ${token}`, + "Content-Type": "multipart/form-data", + }, + }); + } + axios + .post(url, values, { + headers: { + authorization: `Bearer ${token}`, + }, + }) + .then(() => {}) + .catch(() => {}); + }, []); + + const deleteData = useCallback(async (url, rowID) => { + dispatch({ type: "DELETE_DATA", url: url, rowID: rowID }); + axios + .delete(`${url}/${rowID}`, { + headers: { + authorization: `Bearer ${token}`, + }, + }) + .then(() => { + setReloadDataTable(true); + }) + .catch(() => {}); + }, []); + + const editData = useCallback(async (url, values, rowID, image) => { + dispatch({ + type: "EDIT_DATA", + url: url, + values: values, + rowID: rowID, + image: image, + }); + if (image != null) { + var resizedAvatar = await ImageResizer(image.value); + formData.append(image.key, resizedAvatar); + axios.post(`${url}/${rowID}`, formData, { + headers: { + authorization: `Bearer ${token}`, + "Content-Type": "multipart/form-data", + }, + }); + } + axios + .post(`${url}/${rowID}`, values, { + headers: { + authorization: `Bearer ${token}`, + }, + }) + .then(() => {}) + .catch(() => {}); + }, []); + + const toggleDrawer = (open) => { + open == false && + setTimeout(() => { + setDrawerContent(null); + }, theme.transitions.duration.leavingScreen); + setSweepableDrawer({ ...sweepableDrawer, [drawerDirection]: open }); + }; + const handleOpenDeleteDialog = (row) => { + setRowId(row.getValue("id")); + setOpenDeleteDialog(true); + }; + + const handleCloseDeleteDialog = () => { + setOpenDeleteDialog(false); + }; + + const handleDeleteDialog = () => { + setOpenDeleteDialog(false); + }; + + const contextValue = { + toggleDrawer, + drawerDirection, + sweepableDrawer, + setSweepableDrawer, + setDrawerContent, + drawerContent, + openDeleteDialog, + handleOpenDeleteDialog, + handleCloseDeleteDialog, + handleDeleteDialog, + rowId, + deleteData, + addData, + }; + return contextValue; +}; + +export default useDataTable;