431 lines
18 KiB
TypeScript
431 lines
18 KiB
TypeScript
import React, { useEffect, useState, useMemo } from 'react'
|
|
import { createPortal } from 'react-dom';
|
|
import { ToastContainer, toast } from 'react-toastify';
|
|
import BaseButton from '../BaseButton'
|
|
import CardBoxModal from '../CardBoxModal'
|
|
import CardBox from "../CardBox";
|
|
import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/projects/projectsSlice'
|
|
import { useAppDispatch, useAppSelector } from '../../stores/hooks'
|
|
import { useRouter } from 'next/router'
|
|
import { Field, Form, Formik } from "formik";
|
|
import {
|
|
DataGrid,
|
|
GridColDef,
|
|
} from '@mui/x-data-grid';
|
|
import {loadColumns} from "./configureProjectsCols";
|
|
import _ from 'lodash';
|
|
import dataFormatter from '../../helpers/dataFormatter'
|
|
import {dataGridStyles} from "../../styles";
|
|
|
|
const perPage = 100
|
|
|
|
const TableProjects = ({ filterItems, setFilterItems, filters }) => {
|
|
const notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
|
|
|
|
const dispatch = useAppDispatch();
|
|
const router = useRouter();
|
|
|
|
const [id, setId] = useState(null);
|
|
const [currentPage, setCurrentPage] = useState(0);
|
|
const [filterRequest, setFilterRequest] = React.useState('');
|
|
const [columns, setColumns] = useState<GridColDef[]>([]);
|
|
const [selectedRows, setSelectedRows] = useState([]);
|
|
const [sortModel, setSortModel] = useState([
|
|
{
|
|
field: '',
|
|
sort: 'desc',
|
|
},
|
|
]);
|
|
|
|
const { projects, loading, count, notify: projectsNotify, refetch } = useAppSelector((state) => state.projects)
|
|
const { currentUser } = useAppSelector((state) => state.auth);
|
|
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
|
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
|
|
const corners = useAppSelector((state) => state.style.corners);
|
|
|
|
const loadData = async (page = currentPage, request = filterRequest) => {
|
|
if (page !== currentPage) setCurrentPage(page);
|
|
if (request !== filterRequest) setFilterRequest(request);
|
|
const { sort, field } = sortModel[0];
|
|
|
|
const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`;
|
|
dispatch(fetch({ limit: perPage, page, query }));
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (projectsNotify.showNotification) {
|
|
notify(projectsNotify.typeNotification, projectsNotify.textNotification);
|
|
}
|
|
}, [projectsNotify.showNotification]);
|
|
|
|
useEffect(() => {
|
|
if (!currentUser) return;
|
|
loadData();
|
|
}, [sortModel, currentUser]);
|
|
|
|
useEffect(() => {
|
|
if (refetch) {
|
|
loadData(0);
|
|
dispatch(setRefetch(false));
|
|
}
|
|
}, [refetch, dispatch]);
|
|
|
|
const [isModalTrashActive, setIsModalTrashActive] = useState(false)
|
|
|
|
const handleModalAction = () => {
|
|
setIsModalTrashActive(false)
|
|
}
|
|
|
|
const handleDeleteModalAction = (id: string) => {
|
|
setId(id)
|
|
setIsModalTrashActive(true)
|
|
}
|
|
const handleDeleteAction = async () => {
|
|
if (id) {
|
|
await dispatch(deleteItem(id));
|
|
await loadData(0);
|
|
setIsModalTrashActive(false);
|
|
}
|
|
};
|
|
|
|
const generateFilterRequests = useMemo(() => {
|
|
let request = '&';
|
|
filterItems.forEach((item) => {
|
|
const isRangeFilter = filters.find(
|
|
(filter) =>
|
|
filter.title === item.fields.selectedField &&
|
|
(filter.number || filter.date),
|
|
);
|
|
|
|
if (isRangeFilter) {
|
|
const from = item.fields.filterValueFrom;
|
|
const to = item.fields.filterValueTo;
|
|
if (from) {
|
|
request += `${item.fields.selectedField}Range=${from}&`;
|
|
}
|
|
if (to) {
|
|
request += `${item.fields.selectedField}Range=${to}&`;
|
|
}
|
|
} else {
|
|
const value = item.fields.filterValue;
|
|
if (value) {
|
|
request += `${item.fields.selectedField}=${value}&`;
|
|
}
|
|
}
|
|
});
|
|
return request;
|
|
}, [filterItems, filters]);
|
|
|
|
const deleteFilter = (value) => {
|
|
const newItems = filterItems.filter((item) => item.id !== value);
|
|
|
|
if (newItems.length) {
|
|
setFilterItems(newItems);
|
|
} else {
|
|
loadData(0, '');
|
|
setFilterItems(newItems);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = () => {
|
|
loadData(0, generateFilterRequests);
|
|
};
|
|
|
|
const handleChange = (id) => (e) => {
|
|
const value = e.target.value;
|
|
const name = e.target.name;
|
|
|
|
setFilterItems(
|
|
filterItems.map((item) => {
|
|
if (item.id !== id) return item;
|
|
if (name === 'selectedField') return { id, fields: { [name]: value } };
|
|
|
|
return { id, fields: { ...item.fields, [name]: value } }
|
|
}),
|
|
);
|
|
};
|
|
|
|
const handleReset = () => {
|
|
setFilterItems([]);
|
|
loadData(0, '');
|
|
};
|
|
|
|
const onPageChange = (page: number) => {
|
|
loadData(page);
|
|
setCurrentPage(page);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!currentUser) return;
|
|
|
|
loadColumns(
|
|
handleDeleteModalAction,
|
|
`projects`,
|
|
currentUser,
|
|
).then((newCols) => setColumns(newCols));
|
|
}, [currentUser]);
|
|
|
|
const handleTableSubmit = async (id: string, data) => {
|
|
if (!_.isEmpty(data)) {
|
|
await dispatch(update({ id, data }))
|
|
.unwrap()
|
|
.then((res) => res)
|
|
.catch((err) => {
|
|
throw new Error(err);
|
|
});
|
|
}
|
|
};
|
|
|
|
const onDeleteRows = async (selectedRows) => {
|
|
await dispatch(deleteItemsByIds(selectedRows));
|
|
await loadData(0);
|
|
};
|
|
|
|
const controlClasses =
|
|
'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
|
|
` ${bgColor} ${focusRing} ${corners} ` +
|
|
'dark:bg-slate-800 border';
|
|
|
|
return (
|
|
<>
|
|
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
|
|
<CardBox>
|
|
<Formik
|
|
initialValues={{
|
|
checkboxes: ['lorem'],
|
|
switches: ['lorem'],
|
|
radio: 'lorem',
|
|
}}
|
|
onSubmit={() => null}
|
|
>
|
|
<Form>
|
|
<>
|
|
{filterItems && filterItems.map((filterItem) => {
|
|
return (
|
|
<div key={filterItem.id} className="flex mb-4">
|
|
<div className="flex flex-col w-full mr-3">
|
|
<div className=" text-gray-500 font-bold">Filter</div>
|
|
<Field
|
|
className={controlClasses}
|
|
name='selectedField'
|
|
id='selectedField'
|
|
component='select'
|
|
value={filterItem?.fields?.selectedField || ''}
|
|
onChange={handleChange(filterItem.id)}
|
|
>
|
|
{filters.map((selectOption) => (
|
|
<option
|
|
key={selectOption.title}
|
|
value={`${selectOption.title}`}
|
|
>
|
|
{selectOption.label}
|
|
</option>
|
|
))}
|
|
</Field>
|
|
</div>
|
|
{filters.find((filter) =>
|
|
filter.title === filterItem?.fields?.selectedField
|
|
)?.type === 'enum' ? (
|
|
<div className="flex flex-col w-full mr-3">
|
|
<div className="text-gray-500 font-bold">
|
|
Value
|
|
</div>
|
|
<Field
|
|
className={controlClasses}
|
|
name="filterValue"
|
|
id='filterValue'
|
|
component="select"
|
|
value={filterItem?.fields?.filterValue || ''}
|
|
onChange={handleChange(filterItem.id)}
|
|
>
|
|
<option value="">Select Value</option>
|
|
{filters.find((filter) =>
|
|
filter.title === filterItem?.fields?.selectedField
|
|
)?.options?.map((option) => (
|
|
<option key={option} value={option}>
|
|
{option}
|
|
</option>
|
|
))}
|
|
</Field>
|
|
</div>
|
|
) : filters.find((filter) =>
|
|
filter.title === filterItem?.fields?.selectedField
|
|
)?.number ? (
|
|
<div className="flex flex-row w-full mr-3">
|
|
<div className="flex flex-col w-full mr-3">
|
|
<div className=" text-gray-500 font-bold">From</div>
|
|
<Field
|
|
className={controlClasses}
|
|
name='filterValueFrom'
|
|
placeholder='From'
|
|
id='filterValueFrom'
|
|
value={filterItem?.fields?.filterValueFrom || ''}
|
|
onChange={handleChange(filterItem.id)}
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col w-full">
|
|
<div className=" text-gray-500 font-bold">To</div>
|
|
<Field
|
|
className={controlClasses}
|
|
name='filterValueTo'
|
|
placeholder='to'
|
|
id='filterValueTo'
|
|
value={filterItem?.fields?.filterValueTo || ''}
|
|
onChange={handleChange(filterItem.id)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
) : filters.find(
|
|
(filter) =>
|
|
filter.title ===
|
|
filterItem?.fields?.selectedField
|
|
)?.date ? (
|
|
<div className='flex flex-row w-full mr-3'>
|
|
<div className='flex flex-col w-full mr-3'>
|
|
<div className=' text-gray-500 font-bold'>
|
|
From
|
|
</div>
|
|
<Field
|
|
className={controlClasses}
|
|
name='filterValueFrom'
|
|
placeholder='From'
|
|
id='filterValueFrom'
|
|
type='datetime-local'
|
|
value={filterItem?.fields?.filterValueFrom || ''}
|
|
onChange={handleChange(filterItem.id)}
|
|
/>
|
|
</div>
|
|
<div className='flex flex-col w-full'>
|
|
<div className=' text-gray-500 font-bold'>To</div>
|
|
<Field
|
|
className={controlClasses}
|
|
name='filterValueTo'
|
|
placeholder='to'
|
|
id='filterValueTo'
|
|
type='datetime-local'
|
|
value={filterItem?.fields?.filterValueTo || ''}
|
|
onChange={handleChange(filterItem.id)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="flex flex-col w-full mr-3">
|
|
<div className=" text-gray-500 font-bold">Contains</div>
|
|
<Field
|
|
className={controlClasses}
|
|
name='filterValue'
|
|
placeholder='Contained'
|
|
id='filterValue'
|
|
value={filterItem?.fields?.filterValue || ''}
|
|
onChange={handleChange(filterItem.id)}
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className="flex flex-col">
|
|
<div className=" text-gray-500 font-bold">Action</div>
|
|
<BaseButton
|
|
className="my-2"
|
|
type='reset'
|
|
color='danger'
|
|
label='Delete'
|
|
onClick={() => {
|
|
deleteFilter(filterItem.id)
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
<div className="flex">
|
|
<BaseButton
|
|
className="my-2 mr-3"
|
|
color="success"
|
|
label='Apply'
|
|
onClick={handleSubmit}
|
|
/>
|
|
<BaseButton
|
|
className="my-2"
|
|
color='info'
|
|
label='Cancel'
|
|
onClick={handleReset}
|
|
/>
|
|
</div>
|
|
</>
|
|
</Form>
|
|
</Formik>
|
|
</CardBox> : null
|
|
}
|
|
<CardBoxModal
|
|
title="Please confirm"
|
|
buttonColor="info"
|
|
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
|
|
isActive={isModalTrashActive}
|
|
onConfirm={handleDeleteAction}
|
|
onCancel={handleModalAction}
|
|
>
|
|
<p>Are you sure you want to delete this item?</p>
|
|
</CardBoxModal>
|
|
|
|
<div className='relative overflow-x-auto'>
|
|
<DataGrid
|
|
autoHeight
|
|
rowHeight={64}
|
|
sx={dataGridStyles}
|
|
className={'datagrid--table'}
|
|
getRowClassName={() => `datagrid--row`}
|
|
rows={projects ?? []}
|
|
columns={columns}
|
|
initialState={{
|
|
pagination: {
|
|
paginationModel: {
|
|
pageSize: 10,
|
|
},
|
|
},
|
|
}}
|
|
disableRowSelectionOnClick
|
|
processRowUpdate={async (newRow, oldRow) => {
|
|
const data = dataFormatter.dataGridEditFormatter(newRow);
|
|
try {
|
|
await handleTableSubmit(newRow.id, data);
|
|
return newRow;
|
|
} catch {
|
|
return oldRow;
|
|
}
|
|
}}
|
|
sortingMode={'server'}
|
|
checkboxSelection
|
|
onRowSelectionModelChange={(ids) => {
|
|
setSelectedRows(ids)
|
|
}}
|
|
onSortModelChange={(params) => {
|
|
params.length
|
|
? setSortModel(params)
|
|
: setSortModel([{ field: '', sort: 'desc' }]);
|
|
}}
|
|
rowCount={count}
|
|
pageSizeOptions={[10]}
|
|
paginationMode={'server'}
|
|
loading={loading}
|
|
onPaginationModelChange={(params) => {
|
|
onPageChange(params.page);
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{selectedRows.length > 0 &&
|
|
createPortal(
|
|
<BaseButton
|
|
className='me-4'
|
|
color='danger'
|
|
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
|
|
onClick={() => onDeleteRows(selectedRows)}
|
|
/>,
|
|
document.getElementById('delete-rows-button'),
|
|
)}
|
|
<ToastContainer />
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default TableProjects
|