Merge branch 'release/v0.0.1'

This commit is contained in:
Amirhossein Mahmoodi
2024-04-29 15:36:16 +03:30
114 changed files with 5067 additions and 18 deletions

View File

@@ -0,0 +1,18 @@
module.exports = {
apps: [{
name: "rms-v3",
script: 'node_modules/next/dist/bin/next', // cluster mode run with node only, not npm
args: 'start -p 3000',
exec_mode: "cluster", // default fork
instances: "max",
kill_timeout: 4000,
wait_ready: true,
autorestart: true,
watch: false,
max_memory_restart: "1G",
log_date_format: "YYYY-MM-DD HH:mm Z",
env_prod: {
APP_ENV: 'prod' // APP_ENV=prod
}
}],
};

View File

@@ -1,7 +1,12 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
"@/*": [
"./src/*"
],
"^/*": [
"./public/*"
]
}
}
}

View File

@@ -15,9 +15,17 @@
"@mui/icons-material": "^5.15.6",
"@mui/material": "^5.15.6",
"@mui/material-nextjs": "^5.15.6",
"@mui/x-date-pickers": "^6.19.5",
"date-fns": "^3.3.1",
"date-fns-jalali": "^2.13.0-0",
"dayjs": "^1.11.10",
"formik": "^2.4.5",
"jalali-moment": "^3.3.11",
"material-react-table": "^2.11.2",
"next": "14.1.0",
"react": "^18",
"react-dom": "^18",
"sass": "^1.75.0",
"stylis": "^4.3.1",
"stylis-plugin-rtl": "^2.1.1"
},

176
public/fontiran.scss Normal file
View File

@@ -0,0 +1,176 @@
/**
*
* Name: IRAN Sans-Serif Font
* Version: 5.0
* Author: Moslem Ebrahimi (moslemebrahimi.com)
* Created on: Dec 25, 2012
* Updated on: Sep 01, 2017
* Website: http://fontiran.com
* Copyright: Commercial/Proprietary Software
--------------------------------------------------------------------------------------
فونت های ایران سن سریف یک نرم افزار مالکیتی محسوب می شود. جهت آگاهی از قوانین استفاده از این فونت ها لطفا به وب سایت (فونت ایران دات کام) مراجعه نمایید
--------------------------------------------------------------------------------------
IRAN Sans-serif fonts are considered a proprietary software. To gain information about the laws regarding the use of these fonts, please visit www.fontiran.com
--------------------------------------------------------------------------------------
This set of fonts are used in this project under the license: (.....)
--------------------------------------------------------------------------------------
*
**/
@font-face {
font-family: IRANSansFaNum;
font-style: normal;
font-weight: 900;
src: url('./fonts/eot/IRANSansWeb(FaNum)_Black.eot');
src: url('./fonts/eot/IRANSansWeb(FaNum)_Black.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb(FaNum)_Black.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb(FaNum)_Black.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb(FaNum)_Black.ttf') format('truetype');
}
@font-face {
font-family: IRANSansFaNum;
font-style: normal;
font-weight: bold;
src: url('./fonts/eot/IRANSansWeb(FaNum)_Bold.eot');
src: url('./fonts/eot/IRANSansWeb(FaNum)_Bold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb(FaNum)_Bold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf') format('truetype');
}
@font-face {
font-family: IRANSansFaNum;
font-style: normal;
font-weight: 500;
src: url('./fonts/eot/IRANSansWeb(FaNum)_Medium.eot');
src: url('./fonts/eot/IRANSansWeb(FaNum)_Medium.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb(FaNum)_Medium.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf') format('truetype');
}
@font-face {
font-family: IRANSansFaNum;
font-style: normal;
font-weight: 300;
src: url('./fonts/eot/IRANSansWeb(FaNum)_Light.eot');
src: url('./fonts/eot/IRANSansWeb(FaNum)_Light.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb(FaNum)_Light.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb(FaNum)_Light.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb(FaNum)_Light.ttf') format('truetype');
}
@font-face {
font-family: IRANSansFaNum;
font-style: normal;
font-weight: 200;
src: url('./fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot');
src: url('./fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf') format('truetype');
}
@font-face {
font-family: IRANSansFaNum;
font-style: normal;
font-weight: normal;
src: url('./fonts/eot/IRANSansWeb(FaNum).eot');
src: url('./fonts/eot/IRANSansWeb(FaNum).eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb(FaNum).woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb(FaNum).woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb(FaNum).ttf') format('truetype');
}
@font-face {
font-family: IRANSans;
font-style: normal;
font-weight: 900;
src: url('./fonts/eot/IRANSansWeb_Black.eot');
src: url('./fonts/eot/IRANSansWeb_Black.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb_Black.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb_Black.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb_Black.ttf') format('truetype');
}
@font-face {
font-family: IRANSans;
font-style: normal;
font-weight: bold;
src: url('./fonts/eot/IRANSansWeb_Bold.eot');
src: url('./fonts/eot/IRANSansWeb_Bold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb_Bold.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb_Bold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb_Bold.ttf') format('truetype');
}
@font-face {
font-family: IRANSans;
font-style: normal;
font-weight: 500;
src: url('./fonts/eot/IRANSansWeb_Medium.eot');
src: url('./fonts/eot/IRANSansWeb_Medium.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb_Medium.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb_Medium.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb_Medium.ttf') format('truetype');
}
@font-face {
font-family: IRANSans;
font-style: normal;
font-weight: 300;
src: url('./fonts/eot/IRANSansWeb_Light.eot');
src: url('./fonts/eot/IRANSansWeb_Light.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb_Light.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb_Light.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb_Light.ttf') format('truetype');
}
@font-face {
font-family: IRANSans;
font-style: normal;
font-weight: 200;
src: url('./fonts/eot/IRANSansWeb_UltraLight.eot');
src: url('./fonts/eot/IRANSansWeb_UltraLight.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb_UltraLight.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb_UltraLight.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb_UltraLight.ttf') format('truetype');
}
@font-face {
font-family: IRANSans;
font-style: normal;
font-weight: normal;
src: url('./fonts/eot/IRANSansWeb.eot');
src: url('./fonts/eot/IRANSansWeb.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff2/IRANSansWeb.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
url('./fonts/woff/IRANSansWeb.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/IRANSansWeb.ttf') format('truetype');
}
@font-face {
font-family: Parastoo;
font-style: normal;
font-weight: normal;
src: url('./fonts/eot/Parastoo.eot');
src: url('./fonts/eot/Parastoo.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff/Parastoo.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/Parastoo.ttf') format('truetype');
}
@font-face {
font-family: Parastoo;
font-style: normal;
font-weight: bold;
src: url('./fonts/eot/Parastoo-Bold.eot');
src: url('./fonts/eot/Parastoo-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
url('./fonts/woff/Parastoo-Bold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
url('./fonts/ttf/Parastoo-Bold.ttf') format('truetype');
}
@font-face {
font-family: Bnazanin;
font-style: normal;
font-weight: normal;
src: url('./fonts/ttf/BNazanin.ttf') format('truetype');
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
public/global.scss Normal file
View File

@@ -0,0 +1 @@
@import "fontiran";

View File

@@ -1,11 +1,16 @@
import {Box, Stack} from "@mui/material";
import HeaderWithLogo from "@/components/layouts/dashboard/headerWithLogo";
import HeaderWithSidebar from "@/components/layouts/dashboard/headerWithSidebar";
const Template = ({children}) => {
return (
<>
header
sidebar
{children}
</>
)
return (<Stack>
<HeaderWithLogo/>
<Box>
<HeaderWithSidebar>
{children}
</HeaderWithSidebar>
</Box>
</Stack>)
}
export default Template

View File

@@ -1,11 +1,17 @@
import {TableSettingProvider} from "@/lib/contexts/tableSetting";
export const metadata = {
title: 'سامانه جامع راهداری',
}
export default function RootLayout({children}) {
return (<html lang="fa" dir={'rtl'}>
<body>
{children}
</body>
</html>)
return (
<html lang="fa" dir={'rtl'}>
<body style={{height: '100vh'}}>
<TableSettingProvider>
{children}
</TableSettingProvider>
</body>
</html>
)
}

View File

@@ -1,5 +1,148 @@
const Page = () => {
return (<></>)
'use client'
import DataTable from "@/core/components/DataTable";
import { useMemo } from "react";
function Page() {
const columns = useMemo(
() => [
{
accessorKey: 'fullname',
header: 'نام کامل',
id: "fullname",
columns: [
{
accessorKey: 'name.firstName',
header: 'نام',
id: "firstName",
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
enableClickToCopy: true,
columnFilterModeOptions: [
"equals",
"notEquals",
"contains"
],
},
{
accessorKey: 'name.lastName',
header: 'نام خانوادگی',
id: "lastName",
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
columnFilterModeOptions: [
"equals",
"notEquals",
"contains"
],
},
],
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
columnFilterModeOptions: [
"equals",
"notEquals",
"contains"
],
},
{
accessorKey: 'address',
header: 'آدرس',
id: "address",
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
columnFilterModeOptions: [
"equals",
"notEquals",
"contains"
],
},
{
accessorKey: 'city',
header: 'شهر',
id: "city",
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
columnFilterModeOptions: [
"equals",
"notEquals",
"contains",
"lessThan",
"greaterThan",
"between",
],
},
{
accessorKey: 'state',
header: 'وضعیت',
id: "state",
text: 'نام',
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
columnFilterModeOptions: [
"equals",
"notEquals",
"contains"
],
},
{
accessorKey: 'state',
header: 'محدوده',
id: "betweenNum",
text: 'محدوده',
enableColumnFilter: true,
datatype: "text",
filterFn: "betweenNum",
columnFilterModeOptions: [],
},
{
accessorKey: 'state',
header: 'تاریخ',
id: "betweenDate",
text: 'تاریخ',
enableColumnFilter: true,
datatype: "text",
filterFn: "betweenDate",
columnFilterModeOptions: [],
},
{
accessorKey: 'state',
header: 'شلکت',
id: "select2",
text: 'تاریخ',
type: 'select2',
enableColumnFilter: true,
datatype: "text",
filterFn: "equals",
columnFilterModeOptions: [],
},
{
accessorKey: 'state',
header: 'سلکت',
id: "select",
text: 'تنتنت',
type: 'select',
enableColumnFilter: true,
datatype: "text",
filterFn: "equals",
columnFilterModeOptions: [],
},
],
[],
);
return (
<>
<DataTable columns={columns} userId={0} pageName={'testPage'} tableName={'testTable'} />
</>
);
}
export default Page
export default Page;

View File

@@ -2,6 +2,7 @@ import cacheProviderRtl from "@/core/utils/cacheRtl";
import {CssBaseline, ThemeProvider} from "@mui/material";
import theme from "@/core/utils/theme";
import {AppRouterCacheProvider} from "@mui/material-nextjs/v14-appRouter";
import "^/global.scss"
const Template = ({children}) => {
return (

View File

@@ -0,0 +1,31 @@
import {Grid} from '@mui/material';
import MuiDatePicker from "@/components/MuiDatePicker";
import {useEffect, useState} from "react";
const BetWeenDateFilter = ({formik, id , defaultValue}) => {
useEffect(() => {
formik.setFieldValue(`${id}[0]`, defaultValue?.[0] || '');
formik.setFieldValue(`${id}[1]`, defaultValue?.[1] || '');
}, []);
const [dateRange, setDateRange] = useState(defaultValue);
const handleDateChange = (index, value) => {
const newRange = [...dateRange];
newRange[index] = value;
setDateRange(newRange);
};
return (
<Grid container spacing={1} mt={1} mb={2}>
<Grid item xs={6}>
<MuiDatePicker label="تاریخ شروع " name={`${id}[0]`} formik={formik} defaultValue={defaultValue?.[0]} minDate={null} maxDate={dateRange[1]} onDateChange={(value) => handleDateChange(0, value)}/>
</Grid>
<Grid item xs={6}>
<MuiDatePicker label="تاریخ پایان" name={`${id}[1]`} formik={formik} defaultValue={defaultValue?.[1]} minDate={dateRange[0]} maxDate={null} onDateChange={(value) => handleDateChange(1, value)}/>
</Grid>
</Grid>
);
};
export default BetWeenDateFilter;

View File

@@ -0,0 +1,58 @@
import {Grid, TextField} from '@mui/material';
import {useEffect} from "react";
const BetweenNumberFilter = ({formik, id , defaultValue}) => {
useEffect(() => {
formik.setFieldValue(`${id}[0]`, defaultValue?.[0] || '');
formik.setFieldValue(`${id}[1]`, defaultValue?.[1] || '');
}, []);
const handleBlur = (index) => () => {
const minValue = formik.values[id][0];
const maxValue = formik.values[id][1];
if (index === 0 && minValue !== '' && maxValue !== '' && parseInt(minValue) > parseInt(maxValue)) {
formik.setFieldValue(`${id}[0]`, maxValue);
}
if (index === 1 && minValue !== '' && maxValue !== '' && parseInt(maxValue) < parseInt(minValue)) {
formik.setFieldValue(`${id}[1]`, minValue);
}
};
return (
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
name={`${id}[0]`}
label="کمترین رقم"
id={`${id}_min`}
onChange={formik.handleChange}
onBlur={handleBlur(0)}
fullWidth
value={formik.values[id][0] || ''}
margin="normal"
sx={{
position: 'relative'
}}
/>
</Grid>
<Grid item xs={6}>
<TextField
name={`${id}[1]`}
label="بیشترین رقم"
id={`${id}_max`}
onChange={formik.handleChange}
onBlur={handleBlur(1)}
fullWidth
value={formik.values[id][1] || ''}
margin="normal"
sx={{
position: 'relative'
}}
/>
</Grid>
</Grid>
);
};
export default BetweenNumberFilter;

View File

@@ -0,0 +1,20 @@
import FilterColumn from "@/components/FilterColumn";
const CustomToolbar = ({columns}) => {
return (
<FilterColumn columns={columns}/>
)
}
export default CustomToolbar

View File

@@ -0,0 +1,141 @@
import DataTableTest from "@/components/DataTableTest";
import FilterColumn from "@/components/FilterColumn";
import {useMemo} from "react";
import {Button} from "@mui/material";
function DataTableMain() {
const columns = useMemo(
() => [
{
accessorKey: 'name.firstName',
header: 'نام',
size: 150,
id: "firstName",
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
columnFilterModeOptions: [
"equals",
"notEquals",
"contains"
],
},
{
accessorKey: 'name.lastName',
header: 'نام خانوادگی',
size: 150,
id: "lastName",
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
columnFilterModeOptions: [
"equals",
"notEquals",
"contains"
],
},
{
accessorKey: 'address',
header: 'آدرس',
size: 200,
id: "address",
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
columnFilterModeOptions: [
"equals",
"notEquals",
"contains"
],
},
{
accessorKey: 'city',
header: 'شهر',
size: 150,
id: "city",
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
columnFilterModeOptions: [
"equals",
"notEquals",
"contains",
"lessThan",
"greaterThan",
"between",
],
},
{
accessorKey: 'state',
header: 'وضعیت',
size: 150,
id: "state",
text:'نام',
enableColumnFilter: true,
datatype: "text",
filterFn: "contains",
columnFilterModeOptions: [
"equals",
"notEquals",
"contains"
],
},
{
accessorKey: 'state',
header: 'محدوده',
size: 150,
id: "betweenNum",
text:'محدوده',
enableColumnFilter: true,
datatype: "text",
filterFn: "betweenNum",
columnFilterModeOptions: [],
},
{
accessorKey: 'state',
header: 'تاریخ',
size: 150,
id: "betweenDate",
text:'تاریخ',
enableColumnFilter: true,
datatype: "text",
filterFn: "betweenDate",
columnFilterModeOptions: [],
},
{
accessorKey: 'state',
header: 'شلکت',
size: 150,
id: "select2",
text:'تاریخ',
type: 'select2',
enableColumnFilter: true,
datatype: "text",
filterFn: "equals",
columnFilterModeOptions: [],
},
{
accessorKey: 'state',
header: 'سلکت',
size: 150,
id: "select",
text:'تنتنت',
type: 'select',
enableColumnFilter: true,
datatype: "text",
filterFn: "equals",
columnFilterModeOptions: [],
},
],
[],
);
return (
<>
<DataTableTest columns={columns}/>
</>
);
}
export default DataTableMain;

View File

@@ -0,0 +1,104 @@
"use client"
import {useMemo, useState} from 'react';
import {MaterialReactTable} from 'material-react-table';
import useTableSetting from "@/lib/hooks/useTableSetting";
import {IconButton, Tooltip, Typography} from "@mui/material";
import CustomToolbar from "@/components/CustomToolbar";
const data = [
{
name: {
firstName: 'John',
lastName: 'Doe',
},
address: '261 Erdman Ford',
city: 'East Daphne',
state: 'Kentucky',
},
{
name: {
firstName: 'Jane',
lastName: 'Doe',
},
address: '769 Dominic Grove',
city: 'Columbus',
state: 'Ohio',
},
{
name: {
firstName: 'Joe',
lastName: 'Doe',
},
address: '566 Brakus Inlet',
city: 'South Linda',
state: 'West Virginia',
},
{
name: {
firstName: 'Kevin',
lastName: 'Vandy',
},
address: '722 Emie Stream',
city: 'Lincoln',
state: 'Nebraska',
},
{
name: {
firstName: 'Joshua',
lastName: 'Rolluffs',
},
address: '32188 Larkin Turnpike',
city: 'Charleston',
state: 'South Carolina',
},
];
function RefreshIcon() {
return null;
}
const TestDataTable = ({columns}) => {
const [userId, setUserId] = useState(2);
const [pageName, setPageName] = useState('loan');
const [tableName, setTableName] = useState('loan_table');
const {settingStore, hideAction, filterAction, sortAction,} = useTableSetting();
const onColumnVisibilityChange = (event) => {
const settingValue = event();
hideAction(userId, pageName, tableName, settingValue, columns);
};
const onSortingChange = (event) => {
const settingValue = event(settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []);
sortAction(userId, pageName, tableName, settingValue, columns);
}
return (
<>
<Typography>{settingStore?.[userId]?.[pageName]?.[tableName]?.['summary']}</Typography>
<MaterialReactTable
columns={columns}
data={data}
manualSorting={true}
renderTopToolbarCustomActions={({table}) => (<>
<CustomToolbar
columns={columns}
/>
</>)}
initialState={{
columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'],
sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []
}}
state={{
columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'],
sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []
}}
onColumnVisibilityChange={onColumnVisibilityChange}
onSortingChange={onSortingChange}
/>
</>
);
};
export default TestDataTable;

View File

@@ -0,0 +1,30 @@
import { Popper, Paper, List, ListItem, ListItemText, ClickAwayListener } from '@mui/material';
function FilterBox({anchorEl , open = false, handleClose = () => {} , columnFilterModeOptions , onSelectFilter , defaultFilter}) {
const id = open ? 'simple-popper' : undefined;
const handleSelectFilter = (filterFn) => {
onSelectFilter(filterFn);
handleClose();
};
return (
<div>
<Popper id={id} open={open} anchorEl={anchorEl} placement="bottom" style={{ zIndex: 1301 }}>
<ClickAwayListener onClickAway={handleClose}>
<Paper>
<List>
{columnFilterModeOptions.map((option, index) => (
<ListItem key={index} onClick={() => handleSelectFilter(option)} selected={option === defaultFilter} sx={{ cursor: 'pointer' }}>
<ListItemText primary={option} />
</ListItem>
))}
</List>
</Paper>
</ClickAwayListener>
</Popper>
</div>
);
}
export default FilterBox;

View File

@@ -0,0 +1,204 @@
"use client"
import {
Box,
Button, Divider, Drawer,
FormControl, IconButton, styled, Typography
} from '@mui/material';
import {useFormik} from 'formik';
import TextFieldWithFnBox from "@/components/TextFieldWithFnBox";
import {useEffect, useState} from "react";
import useTableSetting from "@/lib/hooks/useTableSetting";
import BetweenNumberFilter from "@/components/BetweenNumberFilter";
import BetWeenDateFilter from "@/components/BetweenDateFilter";
import SelectBoxMultiple from "@/components/SelectBoxMultiple";
import SelectBox from "@/components/SelectBox";
import CloseIcon from '@mui/icons-material/Close';
import { useTheme } from '@mui/material/styles';
function FilterColumn({ columns }) {
const [open, setOpen] = useState(false);
const [selectedFilters, setSelectedFilters] = useState([]);
const [userId, setUserId] = useState(2);
const [pageName, setPageName] = useState('loan');
const [tableName, setTableName] = useState('loan_table');
const {settingStore, hideAction, filterAction, sortAction,} = useTableSetting();
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
padding: theme.spacing(0, 1),
justifyContent: 'space-between',
}));
const toggleDrawer = (newOpen) => () => {
setOpen(newOpen);
};
const handleDrawerClose = () => {
setOpen(false);
};
useEffect(() => {
const settingValue = settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || [];
setSelectedFilters(settingValue.length > 0 ? settingValue : []);
}, [settingStore]);
const handleSelectFilter = (fieldName, filterFn) => {
const filterIndex = selectedFilters.findIndex(filter => filter.id === fieldName);
if (filterIndex !== -1) {
const updatedFilters = [...selectedFilters];
updatedFilters[filterIndex].fn = filterFn;
setSelectedFilters(updatedFilters);
} else {
const newFilter = { id: fieldName, fn: filterFn };
const updatedFilters = [...selectedFilters, newFilter];
setSelectedFilters(updatedFilters);
}
};
const initialValues = columns.reduce((acc, column) => {
if (column.enableColumnFilter) {
const filter = settingStore?.[userId]?.[pageName]?.[tableName]?.['filters']?.find((filter) => filter.id === column.id);
if (column.filterFn === 'betweenNum' || column.filterFn === 'betweenDate') {
acc[column.id] = filter?.value || ["", ""];
}
else if ( column.type === 'select2' ) {
acc[column.id] = filter?.value || [];
}
else {
acc[column.id] = filter?.value || '';
}
}
return acc;
}, {});
const formik = useFormik({
initialValues,
onSubmit: (values) => {
const updatedFilters = selectedFilters.map(filter => {
const value = values[filter.id];
if (value !== undefined) {
return { ...filter, value };
}
return filter;
});
Object.entries(values).forEach(([fieldName, value]) => {
const filterIndex = updatedFilters.findIndex(filter => filter.id === fieldName);
if (filterIndex === -1 && value !== '' && value !== null && value !== undefined) {
const column = columns.find(col => col.id === fieldName);
updatedFilters.push({ id: fieldName, value, fn: column.filterFn, datatype: column.datatype });
}
});
const newArrayFilter = updatedFilters.filter((filter) => {
const value = values[filter.id];
return (
value !== '' &&
value !== null &&
value !== undefined &&
!(Array.isArray(value) && (value.length === 0 || (value.length === 2 && value.every((v) => v === ''))))
);
});
setSelectedFilters(newArrayFilter);
filterAction(userId, pageName, tableName, newArrayFilter, columns);
}
});
return (
<>
<Button onClick={toggleDrawer(true)}>Open drawer</Button>
<Drawer open={open} onClose={toggleDrawer(false)}
sx={{
"& .MuiDrawer-paper": {
padding: '16px',
overflowY: 'auto',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
scrollbarWidth: 'thin',
width:450,
"&::-webkit-scrollbar": {
width: '8px',
},
"&::-webkit-scrollbar-thumb": {
backgroundColor: '#ccc',
borderRadius: '4px',
},
"@media (max-width: 600px)": {
width: '100%'
},
}
}}
>
<DrawerHeader>
<Typography variant="h6" mb={2}>فیلتر</Typography>
<IconButton onClick={handleDrawerClose}>
<CloseIcon/>
</IconButton>
</DrawerHeader>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<FormControl fullWidth>
{columns.map((column, index) => (
column.enableColumnFilter &&
(column.filterFn === 'betweenDate' ?
<BetWeenDateFilter
key={index}
id={column.id}
formik={formik}
onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)}
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''}
/>
: column.filterFn === 'betweenNum' ?
<BetweenNumberFilter
key={index}
id={column.id}
formik={formik}
onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)}
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ["", ""]}
/>
: column.type === 'select2' ?
<SelectBoxMultiple
key={index}
id={column.id}
formik={formik}
onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)}
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || []}
/>
: column.type === 'select' ?
<SelectBox
key={index}
id={column.id}
formik={formik}
onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)}
defaultValue={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''}
/>
:
<TextFieldWithFnBox
key={index}
id={column.id}
header={column.header}
formik={formik}
defaultFilter={(settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.fn || column.filterFn}
columnFilterModeOptions={column.columnFilterModeOptions}
onSelectFilter={(filterFn) => handleSelectFilter(column.id, filterFn)}
defaultValue={formik.values[column.id] || (settingStore?.[userId]?.[pageName]?.[tableName]?.['filters'] || []).find(filter => filter.id === column.id)?.value || ''}
/>
)
))}
</FormControl>
</Box>
<Box sx={{ mt:2 }}>
<Divider />
<Box sx={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
<Button variant="contained" size="medium" onClick={formik.handleSubmit} sx={{ mt: 1 }}>ثبت</Button>
</Box>
</Box>
</Drawer>
</>
);
}
export default FilterColumn;

View File

@@ -0,0 +1,41 @@
"use client"
import {Typography} from "@mui/material";
const data = [
{
id : "hello",
Subitems : [
{id : "amin",firstName : "amin", lastName:"ali",Subitems : [{id : "shahrokh",shahrokh : "shahrokh"}], hasSubitems : true, showSubitems : false},
{id : "amir",firstName : "amir", lastName:"akbar", hasSubitems : false, showSubitems : false},
],
hasSubitems : true,
showSubitems : false
},
{
id : "hi",
Subitems : [
{id : "ali",firstName : "ali", lastName:"ali", hasSubitems : false, showSubitems : false},
{id : "akbar",firstName : "akbar", lastName:"akbar", hasSubitems : false, showSubitems : false},
],
hasSubitems : true,
showSubitems : false
}
]
const MapTest = () => {
const Amin = data.map((item) => {
return item.hasSubitems ? {...item, Subitems: item.Subitems.map((subitem) => true ? {
...subitem, showSubitems: !item.showSubitems
} : subitem)} : item
})
console.log(Amin)
return (
<>
<Typography>hello</Typography>
</>
)
}
export default MapTest

View File

@@ -0,0 +1,67 @@
import { LocalizationProvider, MobileDateTimePicker } from "@mui/x-date-pickers";
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
import moment from "jalali-moment";
import { faIR } from "@mui/x-date-pickers/locales";
import { Box, IconButton, InputAdornment, TextField } from "@mui/material";
import ClearIcon from "@mui/icons-material/Clear";
import { useState } from "react";
export default function MuiDatePicker({label, name, formik, defaultValue, minDate, maxDate, onDateChange }) {
const [pickerValue, setPickerValue] = useState(defaultValue ? new Date(defaultValue) : null);
return (
<Box sx={{ position: 'relative' }}>
<LocalizationProvider
dateAdapter={AdapterDateFnsJalali}
localeText={
faIR.components.MuiLocalizationProvider.defaultProps.localeText
}
>
<MobileDateTimePicker
key={formik.values[name]}
ampm={false}
onChange={(newValue) => {
const date = new Date(newValue);
const formattedDate = moment(date)
.locale("en")
.format("YYYY-MM-DD HH:mm");
formik.setFieldValue(name, formattedDate);
onDateChange(formattedDate);
setPickerValue(newValue);
}}
slotProps={{
textField: {
placeholder: "تاریخ خود را وارد کنید",
helperText: label,
InputProps: {
endAdornment: (
<InputAdornment position="end">
<IconButton
size="small"
onClick={(event) => {
event.stopPropagation();
formik.setFieldValue(name, "");
setPickerValue(null);
}}
sx={{
color: "#bfbfbf",
'&:hover': {
backgroundColor: 'rgba(189, 189, 189, 0.1)',
},
}}
>
<ClearIcon />
</IconButton>
</InputAdornment>
),
},
},
}}
value={pickerValue}
minDate={minDate ? new Date(minDate) : null}
maxDate={maxDate ? new Date(maxDate) : null}
/>
</LocalizationProvider>
</Box>
);
}

View File

@@ -0,0 +1,53 @@
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select';
import {useEffect, useState} from "react";
const names = [
'Oliver Hansen',
'Van Henry',
'April Tucker',
'Ralph Hubbard',
'Omar Alexander',
'Carlos Abbott',
'Miriam Wagner',
'Bradley Wilkerson',
'Virginia Andrews',
'Kelly Snyder',
];
const SelectBox = ({ formik, id, defaultValue }) => {
useEffect(() => {
formik.setFieldValue(id, defaultValue || '');
}, []);
const [selectedValue, setSelectedValue] = useState(defaultValue || '');
const handleChange = (event) => {
const { value } = event.target;
setSelectedValue(value);
formik.setFieldValue(id, value);
};
return (
<>
<FormControl sx={{ mt: 3 }}>
<InputLabel>انتخاب</InputLabel>
<Select
id={id}
value={selectedValue}
onChange={handleChange}
label="انتخاب"
>
{names.map((name) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</>
);
};
export default SelectBox;

View File

@@ -0,0 +1,116 @@
import {useTheme} from '@mui/material/styles';
import Box from '@mui/material/Box';
import OutlinedInput from '@mui/material/OutlinedInput';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select';
import Chip from '@mui/material/Chip';
import {useEffect, useState} from "react";
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250,
},
},
};
const names = [
'Oliver Hansen',
'Van Henry',
'April Tucker',
'Ralph Hubbard',
'Omar Alexander',
'Carlos Abbott',
'Miriam Wagner',
'Bradley Wilkerson',
'Virginia Andrews',
'Kelly Snyder',
];
function getStyles(name, personName, theme) {
return {
fontWeight:
personName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
const SelectBoxMultiple = ({formik, id, defaultValue}) => {
const theme = useTheme();
const [personName, setPersonName] = useState(defaultValue || []);
useEffect(() => {
defaultValue.forEach((value, index) => {
formik.setFieldValue(`${id}[${index}]`, value || '');
});
}, []);
const handleChange = (event) => {
const {
target: {value},
} = event;
const selectedValues = typeof value === 'string' ? value.split(',').filter(item => item.trim() !== '') : value;
formik.setFieldValue(id, []);
if (selectedValues.length === 0) {
setPersonName([]);
formik.setFieldValue(id, []);
} else {
setPersonName(selectedValues);
selectedValues.forEach((selectedValue, index) => {
formik.setFieldValue(`${id}[${index}]`, selectedValue);
});
}
};
return (
<>
<FormControl sx={{width: '100%'}}>
<InputLabel id={id}>انتخاب</InputLabel>
<Select
id={id}
multiple
value={personName}
onChange={handleChange}
input={<OutlinedInput label="Chip"/>}
renderValue={(selected) => (
<Box sx={{display: 'flex', flexWrap: 'wrap', gap: 0.5}}>
{selected.map((value) => (
<Chip key={value} label={value}/>
))}
</Box>
)}
MenuProps={MenuProps}
sx={{
"&.MuiSelect-select": {
minWidth: 0,
maxWidth: '100%',
overflow: 'hidden',
},
}}
>
{names.map((name) => (
<MenuItem
key={name}
value={name}
style={getStyles(name, personName, theme)}
>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</>
);
};
export default SelectBoxMultiple;

View File

@@ -0,0 +1,51 @@
import {IconButton, Paper, TextField} from '@mui/material';
import ArtTrackIcon from '@mui/icons-material/ArtTrack';
import {useEffect, useState} from "react";
import FilterBox from "@/components/FilterBox";
const TextFieldWithFnBox = ({formik, id , header, defaultValue, defaultFilter , columnFilterModeOptions ,onSelectFilter}) => {
const [anchorEl, setAnchorEl] = useState(null);
const [open, setOpen] = useState(false);
useEffect(() => {
formik.setFieldValue(id, defaultValue || '');
}, []);
const handleClick = (event) => {
setOpen((prevOpen) => !prevOpen);
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const handleClose = () => {
setOpen(false);
setAnchorEl(null);
};
return (
<>
<TextField
name={id}
label={header}
id={id}
onChange={formik.handleChange}
fullWidth
value={formik.values[id]}
margin="normal"
InputProps={{
endAdornment: (
<IconButton onClick={handleClick}>
<ArtTrackIcon />
</IconButton>
)
}}
sx={{
position: 'relative'
}}
/>
<FilterBox anchorEl={anchorEl} open={open} handleClose={handleClose} columnFilterModeOptions={columnFilterModeOptions} onSelectFilter={onSelectFilter} defaultFilter={defaultFilter}/>
</>
);
};
export default TextFieldWithFnBox;

View File

@@ -0,0 +1,9 @@
import {Box, Stack} from "@mui/material";
const HeaderWithLogo = () => {
return (<Stack>
<Box>header1</Box>
<Box>header2</Box>
</Stack>)
}
export default HeaderWithLogo

View File

@@ -0,0 +1,94 @@
'use client'
import {useState} from "react";
import {Box, Drawer, IconButton, Paper, styled, Toolbar, Typography} from "@mui/material";
import MuiAppBar from '@mui/material/AppBar';
import MenuIcon from '@mui/icons-material/Menu';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import SidebarMenu from "@/core/components/SidebarMenu";
const drawerWidth = 260;
const Main = styled(Paper, {shouldForwardProp: (prop) => prop !== 'open'})(({theme, open}) => ({
flexGrow: 1, transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen,
}), marginLeft: `-${drawerWidth}px`, ...(open && {
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen,
}), marginLeft: 0,
}),
}),);
const AppBar = styled(MuiAppBar, {
shouldForwardProp: (prop) => prop !== 'open',
})(({theme, open}) => ({
transition: theme.transitions.create(['margin', 'width'], {
easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.leavingScreen,
}), ...(open && {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: `${drawerWidth}px`,
transition: theme.transitions.create(['margin', 'width'], {
easing: theme.transitions.easing.easeOut, duration: theme.transitions.duration.enteringScreen,
}),
}),
}));
const DrawerHeader = styled('div')(({theme}) => ({
display: 'flex', alignItems: 'center', padding: theme.spacing(0, 1), // necessary for content to be below app bar
...theme.mixins.toolbar, justifyContent: 'flex-end',
}));
const HeaderWithSidebar = ({children}) => {
const [open, setOpen] = useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (<Box sx={{display: 'flex'}}>
<AppBar sx={{position: 'fixed', top: "unset"}} elevation={0} open={open}>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
sx={{mr: 2, ...(open && {display: 'none'})}}
>
<MenuIcon/>
</IconButton>
<Typography variant="h6" noWrap component="div">
menu
</Typography>
</Toolbar>
</AppBar>
<Drawer
sx={{
width: drawerWidth, flexShrink: 0, '& .MuiDrawer-paper': {
width: drawerWidth,
boxSizing: 'border-box',
borderTop: 1,
borderTopColor: 'divider',
top: "unset"
},
}}
variant="persistent"
anchor="left"
open={open}
>
<DrawerHeader>
<IconButton onClick={handleDrawerClose}>
<ChevronRightIcon/>
</IconButton>
</DrawerHeader>
<SidebarMenu />
</Drawer>
<Main elevation={0} open={open}>
<DrawerHeader/>
{children}
</Main>
</Box>)
}
export default HeaderWithSidebar

View File

@@ -0,0 +1,19 @@
import { useMaterialReactTable } from "material-react-table";
import DataTable_Paper from "./table/Paper";
const isTableInstanceProp = (props) => props.table !== undefined;
const DataTable_Main = (props) => {
let table;
if (isTableInstanceProp(props)) {
table = props.table;
} else {
// eslint-disable-next-line react-hooks/rules-of-hooks
table = useMaterialReactTable(props);
}
return <DataTable_Paper table={table} />;
};
export default DataTable_Main;

View File

@@ -0,0 +1,207 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { TableBody, Typography } from "@mui/material";
import { useMRT_RowVirtualizer, useMRT_Rows } from "material-react-table";
import { memo, useMemo } from "react";
import DataTable_TableBodyRow, { Memo_DataTable_TableBodyRow } from "./TableBodyRow";
const DataTable_TableBody = ({
columnVirtualizer,
table,
...rest
}) => {
const {
getBottomRows,
getIsSomeRowsPinned,
getRowModel,
getState,
getTopRows,
options: {
enableStickyFooter,
enableStickyHeader,
layoutMode,
localization,
memoMode,
muiTableBodyProps,
renderDetailPanel,
renderEmptyRowsFallback,
rowPinningDisplayMode,
},
refs: { tableFooterRef, tableHeadRef, tablePaperRef },
} = table;
const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState();
const tableBodyProps = {
...parseFromValuesOrFunc(muiTableBodyProps, { table }),
...rest,
};
const tableHeadHeight =
((enableStickyHeader || isFullScreen) &&
tableHeadRef.current?.clientHeight) ||
0;
const tableFooterHeight =
(enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
const pinnedRowIds = useMemo(() => {
if (!rowPinning.bottom?.length && !rowPinning.top?.length) return [];
return getRowModel()
.rows.filter((row) => row.getIsPinned())
.map((r) => r.id);
}, [rowPinning, getRowModel().rows]);
const rows = useMRT_Rows(table);
const rowVirtualizer = useMRT_RowVirtualizer(table, rows);
const { virtualRows } = rowVirtualizer ?? {};
const commonRowProps = {
columnVirtualizer,
numRows: rows.length,
table,
};
return (
<>
{!rowPinningDisplayMode?.includes('sticky') &&
getIsSomeRowsPinned('top') && (
<TableBody
{...tableBodyProps}
sx={(theme) => ({
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
position: 'sticky',
top: tableHeadHeight - 1,
zIndex: 1,
...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)),
})}
>
{getTopRows().map((row, staticRowIndex) => {
const props = {
...commonRowProps,
row,
staticRowIndex,
};
return memoMode === 'rows' ? (
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
) : (
<DataTable_TableBodyRow key={row.id} {...props} />
);
})}
</TableBody>
)}
<TableBody
{...tableBodyProps}
sx={(theme) => ({
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
height: rowVirtualizer
? `${rowVirtualizer.getTotalSize()}px`
: undefined,
minHeight: !rows.length ? '100px' : undefined,
position: 'relative',
...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)),
})}
>
{tableBodyProps?.children ??
(!rows.length ? (
<tr
style={{
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
}}
>
<td
colSpan={table.getVisibleLeafColumns().length}
style={{
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
}}
>
{renderEmptyRowsFallback?.({ table }) ?? (
<Typography
sx={{
color: 'text.secondary',
fontStyle: 'italic',
maxWidth: `min(100vw, ${tablePaperRef.current?.clientWidth ?? 360
}px)`,
py: '2rem',
textAlign: 'center',
width: '100%',
}}
>
{globalFilter || columnFilters.length
? localization.noResultsFound
: localization.noRecordsToDisplay}
</Typography>
)}
</td>
</tr>
) : (
<>
{(virtualRows ?? rows).map((rowOrVirtualRow, staticRowIndex) => {
let row = rowOrVirtualRow;
if (rowVirtualizer) {
if (renderDetailPanel) {
if (rowOrVirtualRow.index % 2 === 1) {
return null;
} else {
staticRowIndex = rowOrVirtualRow.index / 2;
}
} else {
staticRowIndex = rowOrVirtualRow.index;
}
row = rows[staticRowIndex];
}
const props = {
...commonRowProps,
pinnedRowIds,
row,
rowVirtualizer,
staticRowIndex,
virtualRow: rowVirtualizer
? (rowOrVirtualRow)
: undefined,
};
const key = `${row.id}-${row.index}`;
return memoMode === 'rows' ? (
<Memo_DataTable_TableBodyRow key={key} {...props} />
) : (
<DataTable_TableBodyRow key={key} {...props} />
);
})}
</>
))}
</TableBody>
{!rowPinningDisplayMode?.includes('sticky') &&
getIsSomeRowsPinned('bottom') && (
<TableBody
{...tableBodyProps}
sx={(theme) => ({
bottom: tableFooterHeight - 1,
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
position: 'sticky',
zIndex: 1,
...(parseFromValuesOrFunc(tableBodyProps?.sx, theme)),
})}
>
{getBottomRows().map((row, staticRowIndex) => {
const props = {
...commonRowProps,
row,
staticRowIndex,
};
return memoMode === 'rows' ? (
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
) : (
<DataTable_TableBodyRow key={row.id} {...props} />
);
})}
</TableBody>
)}
</>
);
}
export default DataTable_TableBody
export const Memo_DataTable_TableBody = memo(
DataTable_TableBody,
(prev, next) => prev.table.options.data === next.table.options.data,
);

View File

@@ -0,0 +1,306 @@
import { isCellEditable, openEditingCell } from "material-react-table";
const { parseFromValuesOrFunc, getCommonMRTCellStyles } = require("@/core/utils/utils");
const { useTheme } = require("@emotion/react");
const { TableCell, Skeleton } = require("@mui/material");
const { useState, useEffect, useMemo, memo } = require("react");
const { default: DataTable_CopyButton } = require("../buttons/CopyButton");
const { default: DataTable_TableBodyCellValue } = require("./TableBodyCellValue");
const DataTable_TableBodyCell = ({
cell,
numRows,
rowRef,
staticColumnIndex,
staticRowIndex,
table,
...rest
}) => {
const theme = useTheme();
const {
getState,
options: {
columnResizeDirection,
columnResizeMode,
createDisplayMode,
editDisplayMode,
enableCellActions,
enableClickToCopy,
enableColumnOrdering,
enableColumnPinning,
enableGrouping,
layoutMode,
mrtTheme: { draggingBorderColor },
muiSkeletonProps,
muiTableBodyCellProps,
},
setHoveredColumn,
} = table;
const {
actionCell,
columnSizingInfo,
creatingRow,
density,
draggingColumn,
draggingRow,
editingCell,
editingRow,
hoveredColumn,
hoveredRow,
isLoading,
showSkeletons,
} = getState();
const { column, row } = cell;
const { columnDef } = column;
const { columnDefType } = columnDef;
const args = { cell, column, row, table };
const tableCellProps = {
...parseFromValuesOrFunc(muiTableBodyCellProps, args),
...parseFromValuesOrFunc(columnDef.muiTableBodyCellProps, args),
...rest,
};
const skeletonProps = parseFromValuesOrFunc(muiSkeletonProps, {
cell,
column,
row,
table,
});
const [skeletonWidth, setSkeletonWidth] = useState(100);
useEffect(() => {
if ((!isLoading && !showSkeletons) || skeletonWidth !== 100) return;
const size = column.getSize();
setSkeletonWidth(
columnDefType === 'display'
? size / 2
: Math.round(Math.random() * (size - size / 3) + size / 3),
);
}, [isLoading, showSkeletons]);
const draggingBorders = useMemo(() => {
const isDraggingColumn = draggingColumn?.id === column.id;
const isHoveredColumn = hoveredColumn?.id === column.id;
const isDraggingRow = draggingRow?.id === row.id;
const isHoveredRow = hoveredRow?.id === row.id;
const isFirstColumn = column.getIsFirstColumn();
const isLastColumn = column.getIsLastColumn();
const isLastRow = numRows && staticRowIndex === numRows - 1;
const isResizingColumn = columnSizingInfo.isResizingColumn === column.id;
const showResizeBorder =
isResizingColumn && columnResizeMode === 'onChange';
const borderStyle = showResizeBorder
? `2px solid ${draggingBorderColor} !important`
: isDraggingColumn || isDraggingRow
? `1px dashed ${theme.palette.grey[500]} !important`
: isHoveredColumn || isHoveredRow || isResizingColumn
? `2px dashed ${draggingBorderColor} !important`
: undefined;
if (showResizeBorder) {
return columnResizeDirection === 'ltr'
? { borderRight: borderStyle }
: { borderLeft: borderStyle };
}
return borderStyle
? {
borderBottom:
isDraggingRow || isHoveredRow || (isLastRow && !isResizingColumn)
? borderStyle
: undefined,
borderLeft:
isDraggingColumn ||
isHoveredColumn ||
((isDraggingRow || isHoveredRow) && isFirstColumn)
? borderStyle
: undefined,
borderRight:
isDraggingColumn ||
isHoveredColumn ||
((isDraggingRow || isHoveredRow) && isLastColumn)
? borderStyle
: undefined,
borderTop: isDraggingRow || isHoveredRow ? borderStyle : undefined,
}
: undefined;
}, [
columnSizingInfo.isResizingColumn,
draggingColumn,
draggingRow,
hoveredColumn,
hoveredRow,
staticRowIndex,
]);
const isColumnPinned =
enableColumnPinning &&
columnDef.columnDefType !== 'group' &&
column.getIsPinned();
const isEditable = isCellEditable({ cell, table });
const isEditing =
isEditable &&
!['custom', 'modal'].includes(editDisplayMode) &&
(editDisplayMode === 'table' ||
editingRow?.id === row.id ||
editingCell?.id === cell.id) &&
!row.getIsGrouped();
const isCreating =
isEditable && createDisplayMode === 'row' && creatingRow?.id === row.id;
const showClickToCopyButton =
(parseFromValuesOrFunc(enableClickToCopy, cell) === true ||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) === true) &&
!['context-menu', false].includes(
// @ts-ignore
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell),
);
const isRightClickable = parseFromValuesOrFunc(enableCellActions, cell);
const cellValueProps = {
cell,
table,
};
const handleDoubleClick = (event) => {
tableCellProps?.onDoubleClick?.(event);
openEditingCell({ cell, table });
};
const handleDragEnter = (e) => {
tableCellProps?.onDragEnter?.(e);
if (enableGrouping && hoveredColumn?.id === 'drop-zone') {
setHoveredColumn(null);
}
if (enableColumnOrdering && draggingColumn) {
setHoveredColumn(
columnDef.enableColumnOrdering !== false ? column : null,
);
}
};
const handleDragOver = (e) => {
if (columnDef.enableColumnOrdering !== false) {
e.preventDefault();
}
};
const handleContextMenu = (e) => {
tableCellProps?.onContextMenu?.(e);
if (isRightClickable) {
e.preventDefault();
table.setActionCell(cell);
table.refs.actionCellRef.current = e.currentTarget;
}
};
return (
<TableCell
data-index={staticColumnIndex}
data-pinned={!!isColumnPinned || undefined}
{...tableCellProps}
onContextMenu={handleContextMenu}
onDoubleClick={handleDoubleClick}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
sx={(theme) => ({
'&:hover': {
outline:
actionCell?.id === cell.id ||
(editDisplayMode === 'cell' && isEditable) ||
(editDisplayMode === 'table' && (isCreating || isEditing))
? `1px solid ${theme.palette.grey[500]}`
: undefined,
textOverflow: 'clip',
},
alignItems: layoutMode?.startsWith('grid') ? 'center' : undefined,
cursor: isRightClickable
? 'context-menu'
: isEditable && editDisplayMode === 'cell'
? 'pointer'
: 'inherit',
outline:
actionCell?.id === cell.id
? `1px solid ${theme.palette.grey[500]}`
: undefined,
outlineOffset: '-1px',
overflow: 'hidden',
p:
density === 'compact'
? columnDefType === 'display'
? '0 0.5rem'
: '0.5rem'
: density === 'comfortable'
? columnDefType === 'display'
? '0.5rem 0.75rem'
: '1rem'
: columnDefType === 'display'
? '1rem 1.25rem'
: '1.5rem',
textOverflow: columnDefType !== 'display' ? 'ellipsis' : undefined,
whiteSpace:
row.getIsPinned() || density === 'compact' ? 'nowrap' : 'normal',
...getCommonMRTCellStyles({
column,
table,
tableCellProps,
theme,
}),
...draggingBorders,
})}
>
{tableCellProps.children ?? (
<>
{cell.getIsPlaceholder() ? (
columnDef.PlaceholderCell?.({ cell, column, row, table }) ?? null
) : showSkeletons !== false && (isLoading || showSkeletons) ? (
<Skeleton
animation="wave"
height={20}
width={skeletonWidth}
{...skeletonProps}
/>
) : columnDefType === 'display' &&
(['mrt-row-expand', 'mrt-row-numbers', 'mrt-row-select'].includes(
column.id,
) ||
!row.getIsGrouped()) ? (
columnDef.Cell?.({
cell,
column,
renderedCellValue: cell.renderValue(),
row,
rowRef,
staticColumnIndex,
staticRowIndex,
table,
})
) : showClickToCopyButton && columnDef.enableClickToCopy !== false ? (
<DataTable_CopyButton cell={cell} table={table}>
<DataTable_TableBodyCellValue {...cellValueProps} />
</DataTable_CopyButton>
) : (
<DataTable_TableBodyCellValue {...cellValueProps} />
)}
{cell.getIsGrouped() && !columnDef.GroupedCell && (
<> ({row.subRows?.length})</>
)}
</>
)}
</TableCell>
);
}
export default DataTable_TableBodyCell;
export const Memo_DataTable_TableBodyCell = memo(
DataTable_TableBodyCell,
(prev, next) => next.cell === prev.cell,
);

View File

@@ -0,0 +1,112 @@
import { Box } from "@mui/material";
const allowedTypes = ["string", "number"];
const DataTable_TableBodyCellValue = ({
cell,
rowRef,
staticColumnIndex,
staticRowIndex,
table,
}) => {
const {
getState,
options: {
enableFilterMatchHighlighting,
mrtTheme: { matchHighlightColor },
},
} = table;
const { column, row } = cell;
const { columnDef } = column;
const { globalFilter, globalFilterFn } = getState();
const filterValue = column.getFilterValue();
let renderedCellValue =
cell.getIsAggregated() && columnDef.AggregatedCell
? columnDef.AggregatedCell({
cell,
column,
row,
table,
})
: row.getIsGrouped() && !cell.getIsGrouped()
? null
: cell.getIsGrouped() && columnDef.GroupedCell
? columnDef.GroupedCell({
cell,
column,
row,
table,
})
: undefined;
const isGroupedValue = renderedCellValue !== undefined;
if (!isGroupedValue) {
renderedCellValue = cell.renderValue();
}
if (
enableFilterMatchHighlighting &&
columnDef.enableFilterMatchHighlighting !== false &&
String(renderedCellValue) &&
allowedTypes.includes(typeof renderedCellValue) &&
((filterValue &&
allowedTypes.includes(typeof filterValue) &&
["autocomplete", "text"].includes(columnDef.filterVariant)) ||
(globalFilter &&
allowedTypes.includes(typeof globalFilter) &&
column.getCanGlobalFilter()))
) {
const chunks = highlightWords?.({
matchExactly:
(filterValue ? columnDef._filterFn : globalFilterFn) !== "fuzzy",
query: (filterValue ?? globalFilter ?? "").toString(),
text: renderedCellValue?.toString(),
});
if (chunks?.length > 1 || chunks?.[0]?.match) {
renderedCellValue = (
<span aria-label={renderedCellValue} role="note">
{chunks?.map(({ key, match, text }) => (
<Box
aria-hidden="true"
component="span"
key={key}
sx={
match
? {
backgroundColor: matchHighlightColor,
borderRadius: "2px",
color: (theme) =>
theme.palette.mode === "dark"
? theme.palette.common.white
: theme.palette.common.black,
padding: "2px 1px",
}
: undefined
}
>
{text}
</Box>
)) ?? renderedCellValue}
</span>
);
}
}
if (columnDef.Cell && !isGroupedValue) {
renderedCellValue = columnDef.Cell({
cell,
column,
renderedCellValue,
row,
rowRef,
staticColumnIndex,
staticRowIndex,
table,
});
}
return renderedCellValue;
};
export default DataTable_TableBodyCellValue;

View File

@@ -0,0 +1,254 @@
import { commonCellBeforeAfterStyles, getCommonPinnedCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
import { useTheme } from "@emotion/react";
import { TableRow, alpha, darken, lighten } from "@mui/material";
import { getIsRowSelected } from "material-react-table";
import { memo, useMemo, useRef } from "react";
import DataTable_TableBodyCell, { Memo_DataTable_TableBodyCell } from "./TableBodyCell";
import DataTable_TableDetailPanel from "./TableDetailPanel";
const DataTable_TableBodyRow = ({
columnVirtualizer,
numRows,
pinnedRowIds,
row,
rowVirtualizer,
staticRowIndex,
table,
virtualRow,
...rest
}) => {
const theme = useTheme();
const {
getState,
options: {
enableRowOrdering,
enableRowPinning,
enableStickyFooter,
enableStickyHeader,
layoutMode,
memoMode,
mrtTheme: {
baseBackgroundColor,
pinnedRowBackgroundColor,
selectedRowBackgroundColor,
},
muiTableBodyRowProps,
renderDetailPanel,
rowPinningDisplayMode,
},
refs: { tableFooterRef, tableHeadRef },
setHoveredRow,
} = table;
const {
density,
draggingColumn,
draggingRow,
editingCell,
editingRow,
hoveredRow,
isFullScreen,
rowPinning,
} = getState();
const visibleCells = row.getVisibleCells();
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } =
columnVirtualizer ?? {};
const isRowSelected = getIsRowSelected({ row, table });
const isRowPinned = enableRowPinning && row.getIsPinned();
const isDraggingRow = draggingRow?.id === row.id;
const isHoveredRow = hoveredRow?.id === row.id;
const tableRowProps = {
...parseFromValuesOrFunc(muiTableBodyRowProps, {
row,
staticRowIndex,
table,
}),
...rest,
};
const [bottomPinnedIndex, topPinnedIndex] = useMemo(() => {
if (
!enableRowPinning ||
!rowPinningDisplayMode?.includes('sticky') ||
!pinnedRowIds ||
!row.getIsPinned()
)
return [];
return [
[...pinnedRowIds].reverse().indexOf(row.id),
pinnedRowIds.indexOf(row.id),
];
}, [pinnedRowIds, rowPinning]);
const tableHeadHeight =
((enableStickyHeader || isFullScreen) &&
tableHeadRef.current?.clientHeight) ||
0;
const tableFooterHeight =
(enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
const sx = parseFromValuesOrFunc(tableRowProps?.sx, theme);
const defaultRowHeight =
density === 'compact' ? 37 : density === 'comfortable' ? 53 : 69;
const customRowHeight =
// @ts-ignore
parseInt(tableRowProps?.style?.height ?? sx?.height, 10) || undefined;
const rowHeight = customRowHeight || defaultRowHeight;
const handleDragEnter = (_e) => {
if (enableRowOrdering && draggingRow) {
setHoveredRow(row);
}
};
const handleDragOver = (e) => {
e.preventDefault();
};
const rowRef = useRef(null);
const cellHighlightColor = isRowSelected
? selectedRowBackgroundColor
: isRowPinned
? pinnedRowBackgroundColor
: undefined;
const cellHighlightColorHover =
tableRowProps?.hover !== false
? isRowSelected
? cellHighlightColor
: theme.palette.mode === 'dark'
? `${lighten(baseBackgroundColor, 0.3)}`
: `${darken(baseBackgroundColor, 0.3)}`
: undefined;
return (
<>
<TableRow
data-index={renderDetailPanel ? staticRowIndex * 2 : staticRowIndex}
data-pinned={!!isRowPinned || undefined}
data-selected={isRowSelected || undefined}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
ref={(node) => {
if (node) {
rowRef.current = node;
rowVirtualizer?.measureElement(node);
}
}}
selected={isRowSelected}
{...tableRowProps}
style={{
transform: virtualRow
? `translateY(${virtualRow.start}px)`
: undefined,
...tableRowProps?.style,
}}
sx={(theme) => ({
'&:hover td:after': cellHighlightColorHover
? {
backgroundColor: alpha(cellHighlightColorHover, 0.3),
...commonCellBeforeAfterStyles,
}
: undefined,
backgroundColor: `${baseBackgroundColor} !important`,
bottom:
!virtualRow && bottomPinnedIndex !== undefined && isRowPinned
? `${bottomPinnedIndex * rowHeight +
(enableStickyFooter ? tableFooterHeight - 1 : 0)
}px`
: undefined,
boxSizing: 'border-box',
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
opacity: isRowPinned ? 0.97 : isDraggingRow || isHoveredRow ? 0.5 : 1,
position: virtualRow
? 'absolute'
: rowPinningDisplayMode?.includes('sticky') && isRowPinned
? 'sticky'
: 'relative',
td: {
...getCommonPinnedCellStyles({ table, theme }),
},
'td:after': cellHighlightColor
? {
backgroundColor: cellHighlightColor,
...commonCellBeforeAfterStyles,
}
: undefined,
top: virtualRow
? 0
: topPinnedIndex !== undefined && isRowPinned
? `${topPinnedIndex * rowHeight +
(enableStickyHeader || isFullScreen ? tableHeadHeight - 1 : 0)
}px`
: undefined,
transition: virtualRow ? 'none' : 'all 150ms ease-in-out',
width: '100%',
zIndex:
rowPinningDisplayMode?.includes('sticky') && isRowPinned ? 2 : 0,
...(sx),
})}
>
{virtualPaddingLeft ? (
<td style={{ display: 'flex', width: virtualPaddingLeft }} />
) : null}
{(virtualColumns ?? visibleCells).map(
(cellOrVirtualCell, staticColumnIndex) => {
let cell = cellOrVirtualCell;
if (columnVirtualizer) {
staticColumnIndex = (cellOrVirtualCell).index;
cell = visibleCells[staticColumnIndex];
}
const props = {
cell,
numRows,
rowRef,
staticColumnIndex,
staticRowIndex,
table,
};
return cell ? (
memoMode === 'cells' &&
cell.column.columnDef.columnDefType === 'data' &&
!draggingColumn &&
!draggingRow &&
editingCell?.id !== cell.id &&
editingRow?.id !== row.id ? (
<Memo_DataTable_TableBodyCell key={cell.id} {...props} />
) : (
<DataTable_TableBodyCell key={cell.id} {...props} />
)
) : null;
},
)}
{virtualPaddingRight ? (
<td style={{ display: 'flex', width: virtualPaddingRight }} />
) : null}
</TableRow>
{renderDetailPanel && !row.getIsGrouped() && (
<DataTable_TableDetailPanel
parentRowRef={rowRef}
row={row}
rowVirtualizer={rowVirtualizer}
staticRowIndex={staticRowIndex}
table={table}
virtualRow={virtualRow}
/>
)}
</>
);
}
export default DataTable_TableBodyRow
export const Memo_DataTable_TableBodyRow = memo(
DataTable_TableBodyRow,
(prev, next) =>
prev.row === next.row && prev.staticRowIndex === next.staticRowIndex,
);

View File

@@ -0,0 +1,93 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { Collapse, TableCell, TableRow } from "@mui/material";
const DataTable_TableDetailPanel = ({
parentRowRef,
row,
rowVirtualizer,
staticRowIndex,
table,
virtualRow,
...rest
}) => {
const {
getState,
getVisibleLeafColumns,
options: {
layoutMode,
mrtTheme: { baseBackgroundColor },
muiDetailPanelProps,
muiTableBodyRowProps,
renderDetailPanel,
},
} = table;
const { isLoading } = getState();
const tableRowProps = parseFromValuesOrFunc(muiTableBodyRowProps, {
isDetailPanel: true,
row,
staticRowIndex,
table,
});
const tableCellProps = {
...parseFromValuesOrFunc(muiDetailPanelProps, {
row,
table,
}),
...rest,
};
const DetailPanel = !isLoading && renderDetailPanel?.({ row, table });
return (
<TableRow
className="Mui-TableBodyCell-DetailPanel"
data-index={renderDetailPanel ? staticRowIndex * 2 + 1 : staticRowIndex}
ref={(node) => {
if (node) {
rowVirtualizer?.measureElement?.(node);
}
}}
{...tableRowProps}
sx={(theme) => ({
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
position: virtualRow ? 'absolute' : undefined,
top: virtualRow
? `${parentRowRef.current?.getBoundingClientRect()?.height}px`
: undefined,
transform: virtualRow
? `translateY(${virtualRow?.start}px)`
: undefined,
width: '100%',
...(parseFromValuesOrFunc(tableRowProps?.sx, theme)),
})}
>
<TableCell
className="Mui-TableBodyCell-DetailPanel"
colSpan={getVisibleLeafColumns().length}
{...tableCellProps}
sx={(theme) => ({
backgroundColor: virtualRow ? baseBackgroundColor : undefined,
borderBottom: !row.getIsExpanded() ? 'none' : undefined,
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
py: !!DetailPanel && row.getIsExpanded() ? '1rem' : 0,
transition: !virtualRow ? 'all 150ms ease-in-out' : undefined,
width: `100%`,
...(parseFromValuesOrFunc(tableCellProps?.sx, theme)),
})}
>
{virtualRow ? (
row.getIsExpanded() && DetailPanel
) : (
<Collapse in={row.getIsExpanded()} mountOnEnter unmountOnExit>
{DetailPanel}
</Collapse>
)}
</TableCell>
</TableRow>
);
}
export default DataTable_TableDetailPanel

View File

@@ -0,0 +1,75 @@
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
import { Button, Tooltip } from "@mui/material";
import { useState } from "react";
const DataTable_CopyButton = ({
cell,
table,
...rest
}) => {
const {
options: { localization, muiCopyButtonProps },
} = table;
const { column, row } = cell;
const { columnDef } = column;
const [copied, setCopied] = useState(false);
const handleCopy = (event, text) => {
event.stopPropagation();
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 4000);
};
const buttonProps = {
...parseFromValuesOrFunc(muiCopyButtonProps, {
cell,
column,
row,
table,
}),
...parseFromValuesOrFunc(columnDef.muiCopyButtonProps, {
cell,
column,
row,
table,
}),
...rest,
};
return (
<Tooltip
{...getCommonTooltipProps('top')}
title={
buttonProps?.title ??
(copied ? localization.copiedToClipboard : localization.clickToCopy)
}
>
<Button
onClick={(e) => handleCopy(e, cell.getValue())}
size="small"
type="button"
variant="text"
{...buttonProps}
sx={(theme) => ({
backgroundColor: 'transparent',
border: 'none',
color: 'inherit',
cursor: 'copy',
fontFamily: 'inherit',
fontSize: 'inherit',
letterSpacing: 'inherit',
m: '-0.25rem',
minWidth: 'unset',
py: 0,
textAlign: 'inherit',
textTransform: 'inherit',
...(parseFromValuesOrFunc(buttonProps?.sx, theme)),
})}
title={undefined}
/>
</Tooltip>
);
}
export default DataTable_CopyButton

View File

@@ -0,0 +1,54 @@
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
import { IconButton, Tooltip } from "@mui/material";
const DataTable_GrabHandleButton = ({
location,
table,
...rest
}) => {
const {
options: {
icons: { DragHandleIcon },
localization,
},
} = table;
return (
<Tooltip
{...getCommonTooltipProps('top')}
title={rest?.title ?? localization.move}
>
<IconButton
aria-label={rest.title ?? localization.move}
disableRipple
draggable="true"
size="small"
{...rest}
onClick={(e) => {
e.stopPropagation();
rest?.onClick?.(e);
}}
sx={(theme) => ({
'&:active': {
cursor: 'grabbing',
},
'&:hover': {
backgroundColor: 'transparent',
opacity: 1,
},
cursor: 'grab',
m: '0 -0.1rem',
opacity: location === 'row' ? 1 : 0.5,
p: '2px',
transition: 'all 150ms ease-in-out',
...(parseFromValuesOrFunc(rest?.sx, theme)),
})}
title={undefined}
>
<DragHandleIcon />
</IconButton>
</Tooltip>
);
}
export default DataTable_GrabHandleButton

View File

@@ -0,0 +1,41 @@
import { IconButton, Tooltip } from "@mui/material";
import { useState } from "react";
import DataTable_ShowHideColumnsMenu from "../menus/ShowHideColumnsMenu";
const DataTable_ShowHideColumnsButton = ({ table, ...rest }) => {
const {
options: {
icons: { ViewColumnIcon },
localization,
},
} = table;
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
return (
<>
<Tooltip title={rest?.title ?? localization.showHideColumns}>
<IconButton
aria-label={localization.showHideColumns}
onClick={handleClick}
{...rest}
title={undefined}
>
<ViewColumnIcon />
</IconButton>
</Tooltip>
{anchorEl && (
<DataTable_ShowHideColumnsMenu
anchorEl={anchorEl}
setAnchorEl={setAnchorEl}
table={table}
/>
)}
</>
);
}
export default DataTable_ShowHideColumnsButton

View File

@@ -0,0 +1,4 @@
const DataTable_ToggleFiltersButton = ({ table, ...rest }) => {
return <></>
}
export default DataTable_ToggleFiltersButton

View File

@@ -0,0 +1,63 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { TableHead } from "@mui/material";
import DataTable_TableHeadRow from "./TableHeadRow";
const DataTable_TableHead = ({
columnVirtualizer,
table,
...rest
}) => {
const {
getState,
options: {
enableStickyHeader,
layoutMode,
muiTableHeadProps,
positionToolbarAlertBanner,
},
refs: { tableHeadRef },
} = table;
const { isFullScreen, showAlertBanner } = getState();
const tableHeadProps = {
...parseFromValuesOrFunc(muiTableHeadProps, { table }),
...rest,
};
const stickyHeader = enableStickyHeader || isFullScreen;
return (
<TableHead
{...tableHeadProps}
ref={(ref) => {
tableHeadRef.current = ref;
if (tableHeadProps?.ref) {
// @ts-ignore
tableHeadProps.ref.current = ref;
}
}}
sx={(theme) => ({
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
opacity: 0.97,
position: stickyHeader ? 'sticky' : 'relative',
top: stickyHeader && layoutMode?.startsWith('grid') ? 0 : undefined,
zIndex: stickyHeader ? 2 : undefined,
...(parseFromValuesOrFunc(tableHeadProps?.sx, theme)),
})}
>
{table
.getHeaderGroups()
.map((headerGroup) => (
<DataTable_TableHeadRow
columnVirtualizer={columnVirtualizer}
headerGroup={headerGroup}
key={headerGroup.id}
table={table}
/>
))
}
</TableHead>
);
}
export default DataTable_TableHead

View File

@@ -0,0 +1,283 @@
import { getCommonMRTCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
import { useTheme } from "@emotion/react";
import { Box, TableCell } from "@mui/material";
import { MRT_TableHeadCellSortLabel } from "material-react-table";
import { useMemo } from "react";
const DataTable_TableHeadCell = ({
columnVirtualizer,
header,
staticColumnIndex,
table,
...rest
}) => {
const theme = useTheme();
const {
getState,
options: {
columnResizeDirection,
columnResizeMode,
enableColumnActions,
enableColumnDragging,
enableColumnOrdering,
enableColumnPinning,
enableGrouping,
enableMultiSort,
layoutMode,
mrtTheme: { draggingBorderColor },
muiTableHeadCellProps,
},
refs: { tableHeadCellRefs },
setHoveredColumn,
} = table;
const {
columnSizingInfo,
density,
draggingColumn,
grouping,
hoveredColumn,
showColumnFilters,
} = getState();
const { column } = header;
const { columnDef } = column;
const { columnDefType } = columnDef;
const tableCellProps = {
...parseFromValuesOrFunc(muiTableHeadCellProps, { column, table }),
...parseFromValuesOrFunc(columnDef.muiTableHeadCellProps, {
column,
table,
}),
...rest,
};
const isColumnPinned =
enableColumnPinning &&
columnDef.columnDefType !== 'group' &&
column.getIsPinned();
const showColumnActions =
(enableColumnActions || columnDef.enableColumnActions) &&
columnDef.enableColumnActions !== false;
const showDragHandle =
enableColumnDragging !== false &&
columnDef.enableColumnDragging !== false &&
(enableColumnDragging ||
(enableColumnOrdering && columnDef.enableColumnOrdering !== false) ||
(enableGrouping &&
columnDef.enableGrouping !== false &&
!grouping.includes(column.id)));
const headerPL = useMemo(() => {
let pl = 0;
if (column.getCanSort()) pl += 1;
if (showColumnActions) pl += 1.75;
if (showDragHandle) pl += 1.5;
return pl;
}, [showColumnActions, showDragHandle]);
const draggingBorders = useMemo(() => {
const showResizeBorder =
columnSizingInfo.isResizingColumn === column.id &&
columnResizeMode === 'onChange' &&
!header.subHeaders.length;
const borderStyle = showResizeBorder
? `2px solid ${draggingBorderColor} !important`
: draggingColumn?.id === column.id
? `1px dashed ${theme.palette.grey[500]}`
: hoveredColumn?.id === column.id
? `2px dashed ${draggingBorderColor}`
: undefined;
if (showResizeBorder) {
return columnResizeDirection === 'ltr'
? { borderRight: borderStyle }
: { borderLeft: borderStyle };
}
const draggingBorders = borderStyle
? {
borderLeft: borderStyle,
borderRight: borderStyle,
borderTop: borderStyle,
}
: undefined;
return draggingBorders;
}, [draggingColumn, hoveredColumn, columnSizingInfo.isResizingColumn]);
const handleDragEnter = (_e) => {
if (enableGrouping && hoveredColumn?.id === 'drop-zone') {
setHoveredColumn(null);
}
if (enableColumnOrdering && draggingColumn && columnDefType !== 'group') {
setHoveredColumn(
columnDef.enableColumnOrdering !== false ? column : null,
);
}
};
const handleDragOver = (e) => {
if (columnDef.enableColumnOrdering !== false) {
e.preventDefault();
}
};
const HeaderElement =
parseFromValuesOrFunc(columnDef.Header, {
column,
header,
table,
}) ?? columnDef.header;
const columnRelativeDepth = header.depth - column.depth;
if (
columnRelativeDepth > 1
) {
return null;
}
let rowSpan = 1;
if (header.isPlaceholder) {
const leafs = header.getLeafHeaders();
rowSpan = leafs[leafs.length - 1].depth - header.depth;
}
return (
<TableCell
align={
columnDefType === 'group'
? 'center'
: theme.direction === 'rtl'
? 'right'
: 'left'
}
colSpan={header.colSpan}
rowSpan={rowSpan}
data-index={staticColumnIndex}
data-pinned={!!isColumnPinned || undefined}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
ref={(node) => {
if (node) {
tableHeadCellRefs.current[column.id] = node;
if (columnDefType !== 'group') {
columnVirtualizer?.measureElement?.(node);
}
}
}}
{...tableCellProps}
sx={(theme) => ({
'& :hover': {
'.MuiButtonBase-root': {
opacity: 1,
},
},
flexDirection: layoutMode?.startsWith('grid') ? 'column' : undefined,
fontWeight: 'bold',
overflow: 'visible',
p:
density === 'compact'
? '0.5rem'
: density === 'comfortable'
? columnDefType === 'display'
? '0.75rem'
: '1rem'
: columnDefType === 'display'
? '1rem 1.25rem'
: '1.5rem',
pb:
columnDefType === 'display'
? 0
: showColumnFilters || density === 'compact'
? '0.4rem'
: '0.6rem',
pt:
columnDefType === 'group' || density === 'compact'
? '0.25rem'
: density === 'comfortable'
? '.75rem'
: '1.25rem',
userSelect: enableMultiSort && column.getCanSort() ? 'none' : undefined,
verticalAlign: 'middle',
...getCommonMRTCellStyles({
column,
header,
table,
tableCellProps,
theme,
}),
...draggingBorders,
}
)}
>
{tableCellProps.children ?? (
<Box
className="Mui-TableHeadCell-Content"
sx={{
alignItems: 'center',
display: 'flex',
flexDirection:
tableCellProps?.align === 'right' ? 'row-reverse' : 'row',
justifyContent:
columnDefType === 'group' ||
tableCellProps?.align === 'center'
? 'center'
: column.getCanResize()
? 'space-between'
: 'flex-start',
position: 'relative',
width: '100%',
}}
>
<Box
className="Mui-TableHeadCell-Content-Labels"
onClick={column.getToggleSortingHandler()}
sx={{
alignItems: 'center',
cursor:
column.getCanSort() && columnDefType !== 'group'
? 'pointer'
: undefined,
display: 'flex',
flexDirection:
tableCellProps?.align === 'right' ? 'row-reverse' : 'row',
overflow: columnDefType === 'data' ? 'hidden' : undefined,
pl:
tableCellProps?.align === 'center'
? `${headerPL}rem`
: undefined,
}}
>
<Box
className="Mui-TableHeadCell-Content-Wrapper"
sx={{
'&:hover': {
textOverflow: 'clip',
},
minWidth: `${Math.min(columnDef.header?.length ?? 0, 4)}ch`,
overflow: columnDefType === 'data' ? 'hidden' : undefined,
textOverflow: 'ellipsis',
whiteSpace:
(columnDef.header?.length ?? 0) < 20
? 'nowrap'
: 'normal',
}}
>
{HeaderElement}
</Box>
{(column.getCanSort() && columnDefType !== 'group') && (
<MRT_TableHeadCellSortLabel sx={{ width: 20 }} header={header} table={table} />
)}
</Box>
</Box>
)}
</TableCell>
);
}
export default DataTable_TableHeadCell;

View File

@@ -0,0 +1,76 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { TableRow } from "@mui/material";
import DataTable_TableHeadCell from "./TableHeadCell";
const DataTable_TableHeadRow = ({
columnVirtualizer,
headerGroup,
table,
...rest
}) => {
const {
options: {
enableStickyHeader,
layoutMode,
mrtTheme: { baseBackgroundColor },
muiTableHeadRowProps,
},
} = table;
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } =
columnVirtualizer ?? {};
const tableRowProps = {
...parseFromValuesOrFunc(muiTableHeadRowProps, {
headerGroup,
table,
}),
...rest,
};
return (
<TableRow
{...tableRowProps}
sx={(theme) => ({
backgroundColor: baseBackgroundColor,
display: layoutMode?.startsWith('grid') ? 'flex' : undefined,
position:
enableStickyHeader && layoutMode === 'semantic'
? 'sticky'
: 'relative',
top: 0,
...(parseFromValuesOrFunc(tableRowProps?.sx, theme)),
})}
>
{virtualPaddingLeft ? (
<th style={{ display: 'flex', width: virtualPaddingLeft }} />
) : null}
{(virtualColumns ?? headerGroup.headers).map(
(headerOrVirtualHeader, staticColumnIndex) => {
let header = headerOrVirtualHeader;
if (columnVirtualizer) {
staticColumnIndex = (headerOrVirtualHeader)
.index;
header = headerGroup.headers[staticColumnIndex];
}
return header ? (
<DataTable_TableHeadCell
columnVirtualizer={columnVirtualizer}
header={header}
key={header.id}
staticColumnIndex={staticColumnIndex}
table={table}
/>
) : null;
},
)}
{virtualPaddingRight ? (
<th style={{ display: 'flex', width: virtualPaddingRight }} />
) : null}
</TableRow>
);
}
export default DataTable_TableHeadRow

View File

@@ -0,0 +1,123 @@
import { useMaterialReactTable } from "material-react-table";
import DataTable_Main from "./Main";
import { FA_DATATABLE_LOCALIZATION } from "./localization/fa/datatable";
import CustomToolbar from "@/components/CustomToolbar";
import useTableSetting from "@/lib/hooks/useTableSetting";
import {flattenArrayOfObjects} from "@/core/utils/flattenArrayOfObjects";
const data = [
{
name: {
firstName: 'John',
lastName: 'Doe',
},
address: '261 Erdman Ford',
city: 'East Daphne',
state: 'Kentucky',
},
{
name: {
firstName: 'Jane',
lastName: 'Doe',
},
address: '769 Dominic Grove',
city: 'Columbus',
state: 'Ohio',
},
{
name: {
firstName: 'Joe',
lastName: 'Doe',
},
address: '566 Brakus Inlet',
city: 'South Linda',
state: 'West Virginia',
},
{
name: {
firstName: 'Kevin',
lastName: 'Vandy',
},
address: '722 Emie Stream',
city: 'Lincoln',
state: 'Nebraska',
},
{
name: {
firstName: 'Joshua',
lastName: 'Rolluffs',
},
address: '32188 Larkin Turnpike',
city: 'Charleston',
state: 'South Carolina',
},
];
const DataTable = (props) => {
const { userId, pageName, tableName, columns, initialStateProps } = props
const { settingStore, hideAction, sortAction } = useTableSetting();
const flatColumns = flattenArrayOfObjects(columns,'columns')
const onColumnVisibilityChange = (event) => {
const settingValue = event();
hideAction(userId, pageName, tableName, settingValue, flatColumns);
};
const onSortingChange = (event) => {
const settingValue = event(settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []);
sortAction(userId, pageName, tableName, settingValue, flatColumns);
}
const table = useMaterialReactTable({
localization: FA_DATATABLE_LOCALIZATION,
columns,
data: data,
// manualSorting: true,
renderTopToolbarCustomActions: ({ table }) => (<>
<CustomToolbar
columns={flatColumns}
/>
</>),
initialState: {
density: 'compact',
columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'],
sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || [],
...initialStateProps
},
state: {
columnVisibility: settingStore?.[userId]?.[pageName]?.[tableName]?.['hides'],
sorting: settingStore?.[userId]?.[pageName]?.[tableName]?.['sorts'] || []
},
muiTableHeadProps: {
sx: {
borderTop: "1px solid #e1e1e1",
borderBottom: "2px solid #e1e1e1",
}
},
muiTableHeadCellProps: {
sx: {
color: "primary.main",
borderLeft: "1px solid #e1e1e1",
"&:first-of-type": {
borderLeft: "unset"
}
},
},
muiTableBodyCellProps: {
sx: {
borderLeft: "1px solid #e1e1e1",
"&:first-of-type": {
borderLeft: "unset"
}
},
},
onColumnVisibilityChange: onColumnVisibilityChange,
onSortingChange: onSortingChange,
...props
})
return <DataTable_Main table={table} />;
};
export default DataTable;

View File

@@ -0,0 +1,92 @@
export const FA_DATATABLE_LOCALIZATION = {
actions: "عملیات",
and: "و",
cancel: "لغو",
changeFilterMode: "تغییر حالت فیلتر",
changeSearchMode: "تغییر حالت جستجو",
clearFilter: "پاک کردن فیلتر",
clearSearch: "پاک کردن جستجو",
clearSort: "پاک کردن مرتب سازی",
clickToCopy: "کلیک برای کپی",
collapse: "جمع شدن",
collapseAll: "جمع شدن همه",
columnActions: "عملیات ستون",
copiedToClipboard: "کپی شد",
dropToGroupBy: "رها کردن برای گروه بندی بر اساس {column}",
edit: "ویرایش",
expand: "باز شدن",
expandAll: "باز شدن همه",
filterArrIncludes: "شامل",
filterArrIncludesAll: "شامل همه",
filterArrIncludesSome: "شامل",
filterBetween: "میان",
filterBetweenInclusive: "میان با احتساب هر دو",
filterByColumn: "فیلتر بر اساس {column}",
filterContains: "شامل",
filterEmpty: "خالی",
filterEndsWith: "به پایان می‌رسد با",
filterEquals: "برابر",
filterEqualsString: "برابر",
filterFuzzy: "نزدیک",
filterGreaterThan: "بزرگتر از",
filterGreaterThanOrEqualTo: "بزرگتر یا مساوی",
filterInNumberRange: "میان",
filterIncludesString: "شامل",
filterIncludesStringSensitive: "شامل",
filterLessThan: "کوچکتر از",
filterLessThanOrEqualTo: "کوچکتر یا مساوی",
filterMode: "حالت فیلتر: {filterType}",
filterNotEmpty: "غیر خالی",
filterNotEquals: "نا برابر",
filterStartsWith: "شروع می‌شود با",
filterWeakEquals: "برابر",
filteringByColumn: "فیلتر بر اساس {column} - {filterType} {filterValue}",
goToFirstPage: "رفتن به صفحه اول",
goToLastPage: "رفتن به صفحه آخر",
goToNextPage: "رفتن به صفحه بعدی",
goToPreviousPage: "رفتن به صفحه قبلی",
grab: "گرفتن",
groupByColumn: "گروه بندی بر اساس {column}",
groupedBy: "گروه بندی شده بر اساس",
hideAll: "پنهان کردن همه",
hideColumn: "پنهان کردن ستون {column}",
max: "حداکثر",
min: "حداقل",
move: "انتقال",
noRecordsToDisplay: "هیچ رکوردی برای نمایش وجود ندارد",
noResultsFound: "نتیجه‌ای یافت نشد",
of: "از",
or: "یا",
pinToLeft: "چسباندن به سمت چپ",
pinToRight: "چسباندن به سمت راست",
resetColumnSize: "بازنشانی اندازه ستون",
resetOrder: "بازنشانی ترتیب",
rowActions: "عملیات ردیف",
rowNumber: "#",
rowNumbers: "شماره ردیف",
rowsPerPage: "ردیف در هر صفحه",
save: "ذخیره",
search: "جستجو",
selectedCountOfRowCountRowsSelected:
"{selectedCount} از {rowCount} ردیف انتخاب شده",
select: "انتخاب",
showAll: "نمایش همه",
showAllColumns: "نمایش همه ستون‌ها",
showHideColumns: "نمایش/مخفی کردن ستون‌ها",
showHideFilters: "نمایش/مخفی کردن فیلترها",
showHideSearch: "نمایش/مخفی کردن جستجو",
sortByColumnAsc: "مرتب سازی بر اساس {column} صعودی",
sortByColumnDesc: "مرتب سازی بر اساس {column} نزولی",
sortedByColumnAsc: "مرتب شده بر اساس {column} صعودی",
sortedByColumnDesc: "مرتب شده بر اساس {column} نزولی",
thenBy: "، سپس بر اساس ",
toggleDensity: "تغییر تراکم",
toggleFullScreen: "تغییر حالت تمام صفحه",
toggleSelectAll: "انتخاب/عدم انتخاب همه",
toggleSelectRow: "انتخاب/عدم انتخاب ردیف",
toggleVisibility: "تغییر پیدا/پنهان",
ungroupByColumn: "لغو گروه بندی بر اساس {column}",
unpin: "رها کردن",
unpinAll: "رها کردن همه",
unsorted: "بدون مرتب سازی",
};

View File

@@ -0,0 +1,50 @@
import { Box, IconButton, ListItemIcon, MenuItem } from "@mui/material";
const DataTable_ActionMenuItem = ({
icon,
label,
onOpenSubMenu,
table,
...rest
}) => {
const {
options: {
icons: { ArrowRightIcon },
},
} = table;
return (
<MenuItem
sx={{
alignItems: 'center',
justifyContent: 'space-between',
minWidth: '120px',
my: 0,
py: '6px',
}}
{...rest}
>
<Box
sx={{
alignItems: 'center',
display: 'flex',
}}
>
<ListItemIcon>{icon}</ListItemIcon>
{label}
</Box>
{onOpenSubMenu && (
<IconButton
onClick={onOpenSubMenu}
onMouseEnter={onOpenSubMenu}
size="small"
sx={{ p: 0 }}
>
<ArrowRightIcon />
</IconButton>
)}
</MenuItem>
);
}
export default DataTable_ActionMenuItem

View File

@@ -0,0 +1,100 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { Menu } from "@mui/material";
import DataTable_ActionMenuItem from "./ActionMenuItem";
const DataTable_CellActionMenu = ({
table,
...rest
}) => {
const {
getState,
options: {
editDisplayMode,
enableClickToCopy,
enableEditing,
icons: { ContentCopy, EditIcon },
localization,
mrtTheme: { menuBackgroundColor },
renderCellActionMenuItems,
},
refs: { actionCellRef },
} = table;
const { actionCell, density } = getState();
const cell = actionCell || null;
const { row } = cell;
const { column } = cell;
const { columnDef } = column;
const handleClose = (event) => {
event?.stopPropagation();
table.setActionCell(null);
actionCellRef.current = null;
};
const internalMenuItems = [
(parseFromValuesOrFunc(enableClickToCopy, cell) === 'context-menu' ||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) ===
'context-menu') && (
<DataTable_ActionMenuItem
icon={<ContentCopy />}
key={'mrt-copy'}
label={localization.copy}
onClick={(event) => {
event.stopPropagation();
navigator.clipboard.writeText(cell.getValue());
handleClose();
}}
table={table}
/>
),
parseFromValuesOrFunc(enableEditing, row) && editDisplayMode === 'cell' && (
<DataTable_ActionMenuItem
icon={<EditIcon />}
key={'mrt-edit'}
label={localization.edit}
onClick={() => {
openEditingCell({ cell, table });
handleClose();
}}
table={table}
/>
),
].filter(Boolean);
const renderActionProps = {
cell,
closeMenu: handleClose,
column,
internalMenuItems,
row,
table,
};
const menuItems =
columnDef.renderCellActionMenuItems?.(renderActionProps) ??
renderCellActionMenuItems?.(renderActionProps);
return (
(!!menuItems?.length || !!internalMenuItems?.length) && (
<Menu
MenuListProps={{
dense: density === 'compact',
sx: {
backgroundColor: menuBackgroundColor,
},
}}
anchorEl={actionCellRef.current}
disableScrollLock
onClick={(event) => event.stopPropagation()}
onClose={handleClose}
open={!!cell}
transformOrigin={{ horizontal: -100, vertical: 8 }}
{...rest}
>
{menuItems ?? internalMenuItems}
</Menu>
)
);
}
export default DataTable_CellActionMenu

View File

@@ -0,0 +1,140 @@
import { Box, Button, Divider, Menu } from "@mui/material";
import { useMemo, useState } from "react";
import DataTable_ShowHideColumnsMenuItems from "./ShowHideColumnsMenuItems";
const DataTable_ShowHideColumnsMenu = ({ anchorEl,
setAnchorEl,
table,
...rest
}) => {
const {
getAllColumns,
getAllLeafColumns,
getCenterLeafColumns,
getIsAllColumnsVisible,
getIsSomeColumnsPinned,
getIsSomeColumnsVisible,
getLeftLeafColumns,
getRightLeafColumns,
getState,
options: {
enableColumnOrdering,
enableColumnPinning,
enableHiding,
localization,
mrtTheme: { menuBackgroundColor },
},
} = table;
const { columnOrder, columnPinning, density } = getState();
const handleToggleAllColumns = (value) => {
getAllLeafColumns()
.filter((col) => col.columnDef.enableHiding !== false)
.forEach((col) => col.toggleVisibility(value));
};
const allColumns = useMemo(() => {
const columns = getAllColumns();
if (
columnOrder.length > 0 &&
!columns.some((col) => col.columnDef.columnDefType === 'group')
) {
return [
...getLeftLeafColumns(),
...Array.from(new Set(columnOrder)).map((colId) =>
getCenterLeafColumns().find((col) => col?.id === colId),
),
...getRightLeafColumns(),
].filter(Boolean);
}
return columns;
}, [
columnOrder,
columnPinning,
getAllColumns(),
getCenterLeafColumns(),
getLeftLeafColumns(),
getRightLeafColumns(),
]);
const isNestedColumns = allColumns.some(
(col) => col.columnDef.columnDefType === 'group',
);
const [hoveredColumn, setHoveredColumn] = useState(null);
return (
<Menu
MenuListProps={{
dense: density === 'compact',
sx: {
backgroundColor: menuBackgroundColor,
},
}}
anchorEl={anchorEl}
disableScrollLock
onClose={() => setAnchorEl(null)}
open={!!anchorEl}
{...rest}
>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
p: '0.5rem',
pt: 0,
}}
>
{enableHiding && (
<Button
disabled={!getIsSomeColumnsVisible()}
onClick={() => handleToggleAllColumns(false)}
>
{localization.hideAll}
</Button>
)}
{enableColumnOrdering && (
<Button
onClick={() =>
table.setColumnOrder(
getDefaultColumnOrderIds(table.options, true),
)
}
>
{localization.resetOrder}
</Button>
)}
{enableColumnPinning && (
<Button
disabled={!getIsSomeColumnsPinned()}
onClick={() => table.resetColumnPinning(true)}
>
{localization.unpinAll}
</Button>
)}
{enableHiding && (
<Button
disabled={getIsAllColumnsVisible()}
onClick={() => handleToggleAllColumns(true)}
>
{localization.showAll}
</Button>
)}
</Box>
<Divider />
{allColumns.map((column, index) => (
<DataTable_ShowHideColumnsMenuItems
allColumns={allColumns}
column={column}
hoveredColumn={hoveredColumn}
isNestedColumns={isNestedColumns}
key={`${index}-${column.id}`}
setHoveredColumn={setHoveredColumn}
table={table}
/>
))}
</Menu>
);
}
export default DataTable_ShowHideColumnsMenu

View File

@@ -0,0 +1,160 @@
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
import { Box, FormControlLabel, MenuItem, Switch, Tooltip, Typography } from "@mui/material";
import { useRef, useState } from "react";
import DataTable_GrabHandleButton from "../buttons/GrabHandleButton";
const DataTable_ShowHideColumnsMenuItems = ({
allColumns,
column,
hoveredColumn,
isNestedColumns,
setHoveredColumn,
table,
...rest
}) => {
const {
getState,
options: {
enableColumnOrdering,
enableHiding,
localization,
mrtTheme: { draggingBorderColor },
},
setColumnOrder,
} = table;
const { columnOrder } = getState();
const { columnDef } = column;
const { columnDefType } = columnDef;
const switchChecked = column.getIsVisible();
const handleToggleColumnHidden = (column) => {
if (columnDefType === 'group') {
column?.columns?.forEach?.((childColumn) => {
childColumn.toggleVisibility(!switchChecked);
});
} else {
column.toggleVisibility();
}
};
const menuItemRef = useRef(null);
const [isDragging, setIsDragging] = useState(false);
const handleDragStart = (e) => {
setIsDragging(true);
try {
e.dataTransfer.setDragImage(menuItemRef.current, 0, 0);
} catch (e) {
console.error(e);
}
};
const handleDragEnd = (_e) => {
setIsDragging(false);
setHoveredColumn(null);
if (hoveredColumn) {
setColumnOrder(reorderColumn(column, hoveredColumn, columnOrder));
}
};
const handleDragEnter = (_e) => {
if (!isDragging && columnDef.enableColumnOrdering !== false) {
setHoveredColumn(column);
}
};
if (!columnDef.header || columnDef.visibleInShowHideMenu === false) {
return null;
}
return (
<>
<MenuItem
disableRipple
onDragEnter={handleDragEnter}
ref={menuItemRef}
{...rest}
sx={(theme) => ({
alignItems: 'center',
justifyContent: 'flex-start',
my: 0,
opacity: isDragging ? 0.5 : 1,
outline: isDragging
? `2px dashed ${theme.palette.grey[500]}`
: hoveredColumn?.id === column.id
? `2px dashed ${draggingBorderColor}`
: 'none',
outlineOffset: '-2px',
pl: `${(column.depth + 0.5) * 2}rem`,
py: '6px',
...(parseFromValuesOrFunc(rest?.sx, theme)),
})}
>
<Box
sx={{
display: 'flex',
flexWrap: 'nowrap',
gap: '8px',
}}
>
{columnDefType !== 'group' &&
enableColumnOrdering &&
!isNestedColumns &&
(columnDef.enableColumnOrdering !== false ? (
<DataTable_GrabHandleButton
onDragEnd={handleDragEnd}
onDragStart={handleDragStart}
table={table}
/>
) : (
<Box sx={{ width: '28px' }} />
))}
{enableHiding ? (
<FormControlLabel
checked={switchChecked}
componentsProps={{
typography: {
sx: {
mb: 0,
opacity: columnDefType !== 'display' ? 1 : 0.5,
},
},
}}
control={
<Tooltip
{...getCommonTooltipProps()}
title={localization.toggleVisibility}
>
<Switch />
</Tooltip>
}
disabled={!column.getCanHide()}
label={columnDef.header}
onChange={() => handleToggleColumnHidden(column)}
/>
) : (
<Typography sx={{ alignSelf: 'center' }}>
{columnDef.header}
</Typography>
)}
</Box>
</MenuItem>
{column.columns?.map((c, i) => (
<DataTable_ShowHideColumnsMenuItems
allColumns={allColumns}
column={c}
hoveredColumn={hoveredColumn}
isNestedColumns={isNestedColumns}
key={`${i}-${c.id}`}
setHoveredColumn={setHoveredColumn}
table={table}
/>
))}
</>
);
}
export default DataTable_ShowHideColumnsMenuItems;

View File

@@ -0,0 +1,78 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { Paper } from "@mui/material";
import DataTable_BottomToolbar from "../toolbar/BottomToolbar";
import DataTable_TopToolbar from "../toolbar/TopToolbar";
import DataTable_TableContainer from "./TableContainer";
const DataTable_Paper = ({ table, ...rest }) => {
const {
getState,
options: {
enableBottomToolbar,
enableTopToolbar,
mrtTheme: { baseBackgroundColor },
muiTablePaperProps,
renderBottomToolbar,
renderTopToolbar,
},
refs: { tablePaperRef },
} = table;
const { isFullScreen } = getState();
const paperProps = {
...parseFromValuesOrFunc(muiTablePaperProps, { table }),
...rest,
};
return (
<Paper
elevation={0}
{...paperProps}
ref={(ref) => {
tablePaperRef.current = ref;
if (paperProps?.ref) {
//@ts-ignore
paperProps.ref.current = ref;
}
}}
style={{
...(isFullScreen
? {
bottom: 0,
height: '100dvh',
left: 0,
margin: 0,
maxHeight: '100dvh',
maxWidth: '100dvw',
padding: 0,
position: 'fixed',
right: 0,
top: 0,
width: '100dvw',
zIndex: 999,
}
: {}),
...paperProps?.style,
}}
sx={(theme) => ({
backgroundColor: baseBackgroundColor,
backgroundImage: 'unset',
overflow: 'hidden',
transition: 'all 100ms ease-in-out',
...(parseFromValuesOrFunc(paperProps?.sx, theme)),
})}
>
{enableTopToolbar &&
(parseFromValuesOrFunc(renderTopToolbar, { table }) ?? (
<DataTable_TopToolbar table={table} />
))}
<DataTable_TableContainer table={table} />
{enableBottomToolbar &&
(parseFromValuesOrFunc(renderBottomToolbar, { table }) ?? (
<DataTable_BottomToolbar table={table} />
))}
</Paper>
);
};
export default DataTable_Paper;

View File

@@ -0,0 +1,77 @@
import { parseCSSVarId, parseFromValuesOrFunc } from "@/core/utils/utils";
import { Table } from "@mui/material";
import { useMRT_ColumnVirtualizer } from "material-react-table";
import { useMemo } from "react";
import DataTable_TableBody, { Memo_DataTable_TableBody } from "../body/TableBody";
import DataTable_TableHead from "../head/TableHead";
const DataTable_Table = ({
table,
...rest
}) => {
const {
getFlatHeaders,
getState,
options: {
columns,
enableStickyHeader,
enableTableFooter,
enableTableHead,
layoutMode,
memoMode,
muiTableProps,
renderCaption,
},
} = table;
const { columnSizing, columnSizingInfo, columnVisibility, isFullScreen } =
getState();
const tableProps = {
...parseFromValuesOrFunc(muiTableProps, { table }),
...rest,
};
const Caption = parseFromValuesOrFunc(renderCaption, { table });
const columnSizeVars = useMemo(() => {
const headers = getFlatHeaders();
const colSizes = {};
for (let i = 0; i < headers.length; i++) {
const header = headers[i];
const colSize = header.getSize();
colSizes[`--header-${parseCSSVarId(header.id)}-size`] = colSize;
colSizes[`--col-${parseCSSVarId(header.column.id)}-size`] = colSize;
}
return colSizes;
}, [columns, columnSizing, columnSizingInfo, columnVisibility]);
const columnVirtualizer = useMRT_ColumnVirtualizer(table);
const commonTableGroupProps = {
columnVirtualizer,
table,
};
return (
<Table
stickyHeader={enableStickyHeader || isFullScreen}
{...tableProps}
style={{ ...columnSizeVars, ...tableProps?.style }}
sx={(theme) => ({
display: layoutMode?.startsWith('grid') ? 'grid' : undefined,
position: 'relative',
...(parseFromValuesOrFunc(tableProps?.sx, theme)),
})}
>
{!!Caption && <caption>{Caption}</caption>}
{enableTableHead && <DataTable_TableHead {...commonTableGroupProps} />}
{memoMode === 'table-body' || columnSizingInfo.isResizingColumn ? (
<Memo_DataTable_TableBody {...commonTableGroupProps} />
) : (
<DataTable_TableBody {...commonTableGroupProps} />
)}
{/* {enableTableFooter && <MRT_TableFooter {...commonTableGroupProps} />} */}
</Table>
);
}
export default DataTable_Table

View File

@@ -0,0 +1,94 @@
import { parseFromValuesOrFunc } from "@/core/utils/utils";
import { TableContainer } from "@mui/material";
import { useEffect, useLayoutEffect, useState } from "react";
import DataTable_CellActionMenu from "../menus/CellActionMenu";
import DataTable_Table from "./Table";
import DataTable_TableLoadingOverlay from "./TableLoadingOverlay";
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
const DataTable_TableContainer = ({
table,
...rest
}) => {
const {
getState,
options: {
enableCellActions,
enableStickyHeader,
muiTableContainerProps,
},
refs: { bottomToolbarRef, tableContainerRef, topToolbarRef },
} = table;
const {
actionCell,
isFullScreen,
isLoading,
showLoadingOverlay,
} = getState();
const loading =
showLoadingOverlay !== false && (isLoading || showLoadingOverlay);
const [totalToolbarHeight, setTotalToolbarHeight] = useState(0);
const tableContainerProps = {
...parseFromValuesOrFunc(muiTableContainerProps, {
table,
}),
...rest,
};
useIsomorphicLayoutEffect(() => {
const topToolbarHeight =
typeof document !== 'undefined'
? topToolbarRef.current?.offsetHeight ?? 0
: 0;
const bottomToolbarHeight =
typeof document !== 'undefined'
? bottomToolbarRef?.current?.offsetHeight ?? 0
: 0;
setTotalToolbarHeight(topToolbarHeight + bottomToolbarHeight);
});
return (
<TableContainer
aria-busy={loading}
aria-describedby={loading ? 'mrt-progress' : undefined}
{...tableContainerProps}
ref={(node) => {
if (node) {
tableContainerRef.current = node;
if (tableContainerProps?.ref) {
//@ts-ignore
tableContainerProps.ref.current = node;
}
}
}}
style={{
maxHeight: isFullScreen
? `calc(100vh - ${totalToolbarHeight}px)`
: undefined,
...tableContainerProps?.style,
}}
sx={(theme) => ({
maxHeight: enableStickyHeader
? `clamp(350px, calc(100vh - ${totalToolbarHeight}px), 9999px)`
: undefined,
maxWidth: '100%',
overflow: 'auto',
position: 'relative',
...(parseFromValuesOrFunc(tableContainerProps?.sx, theme)),
})}
>
{loading ? <DataTable_TableLoadingOverlay table={table} /> : null}
<DataTable_Table table={table} />
{enableCellActions && actionCell && <DataTable_CellActionMenu table={table} />}
</TableContainer>
);
}
export default DataTable_TableContainer

Some files were not shown because too many files have changed in this diff Show More