Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

styled MUI data grid #17

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,463 changes: 1,162 additions & 301 deletions package-lock.json

Large diffs are not rendered by default.

13,232 changes: 13,232 additions & 0 deletions packages/admin_view/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion packages/admin_view/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
"@bu-sail/saas-view": "file:../view",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mui/material": "^5.14.8",
"@mui/icons-material": "^6.1.5",
"@mui/material": "^6.1.5",
"firebase": "^11.0.1",
"@mui/x-data-grid": "^7.21.0",
"@mui/x-data-grid-generator": "^7.22.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"styled-components": "^6.1.13",
"react-router-dom": "^6.27.0"
},
"devDependencies": {
Expand Down
96 changes: 96 additions & 0 deletions packages/admin_view/src/components/LexiconTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { GetAllLexEntriesQuery, useGetAllLexEntriesQuery } from '../graphql/lexicon/lexicon.ts';
import { DataGrid, GridColDef,DataGridProps } from '@mui/x-data-grid';
import { useState } from 'react';
import { Box, Stack, Button } from '@mui/material';
import { alpha, createTheme, styled } from '@mui/material/styles';



const tableColumns: GridColDef<GetAllLexEntriesQuery['lexiconAllEntries'][number]>[] = [
{
field: 'primary',
headerName: 'Primary',
width: 200,
headerAlign: 'center',
},
{
field: 'associates',
headerName: 'Associates',
width: 200,
headerAlign: 'center',
},
{
field: 'fields',
headerName: 'Fields',
width: 200,
headerAlign: 'center',

},
{
field: 'key',
headerName: 'Key',
width: 200,
headerAlign: 'center',

},
{
field: 'video',
headerName: 'Video',
width: 200,
headerAlign: 'center',

}
];


const LexiconTable = () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are on the right track here but I think it will be much easier to adapt the crud datagrid example from the MUI docs to our needs, rather than trying to code our own solution from scratch. I would recommend starting with the example at https://mui.com/x/react-data-grid/editing/ and editing it where necessary to handle our data. I think it should be as simple as just changing the GridColDef and adding in the query for the lexicon data.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea having the implementation follow the CRUD sample will make life easier. This is pretty close.

Just to add some pointed areas to have this implementation match the sample.

  1. Having the "Add Row" button as part of the DataGrid slot property like in the demo and removing the "Remove Row` button
  2. Having the "Action" column to handle things like having an edit and delete buttons.

//manage rows
const [rows, setRows] = useState<GetAllLexEntriesQuery['lexiconAllEntries']>([]);

//fetch data
const { data } = useGetAllLexEntriesQuery({ variables: { lexicon: '64b15233e535bc69dc95b92f' } });
const lexiconEntries = data?.lexiconAllEntries || [];

//inital state of number of rows -> temp = 3
const [nRows, setNRows] = useState(3);
const removeRow = () => setNRows((x) => Math.max(0, x - 1));

//needs to be be fixed and updated with actual data
const addRow = () => {
const newRow = {
id: rows.length + 1,
primary: 'new primary',
associates: ['new associates'],
fields: 'new fields',
key: 'new key',
video: 'new video',
};

setRows((prevRows) => [...prevRows, newRow]);
}

return (
<Box sx={{ width: '100%' }}>
<Stack direction="row" spacing={2}>
<Button size="small" onClick={removeRow} disabled={nRows <= 0}>
Remove Row
</Button>
<Button size="small" onClick={addRow}>
Add Row
</Button>
</Stack>

<div style={{display:'flex', flexDirection: 'column', width: '100%'}}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using a div, I recommend using a Box or similar. Then the container will follow the theme provided settings at the global level.

<DataGrid
columns={tableColumns}
rows={lexiconEntries}
getRowId={({ key }) => key}
/>
</div>
</Box>
);
};

export default LexiconTable;


242 changes: 242 additions & 0 deletions packages/admin_view/src/components/LexiconTableTemp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import AddIcon from '@mui/icons-material/Add';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Close';
import {
GridRowsProp,
GridRowModesModel,
GridRowModes,
DataGrid,
GridColDef,
GridToolbarContainer,
GridActionsCellItem,
GridEventListener,
GridRowId,
GridRowEditStopReasons,
GridSlots
} from '@mui/x-data-grid';
import { randomId } from '@mui/x-data-grid-generator';
import { GetAllLexEntriesQuery, useGetAllLexEntriesQuery } from '../graphql/lexicon/lexicon.ts';
import { useEffect, useMemo } from 'react';
import { TextField } from '@mui/material';

interface EditToolbarProps {
setRows: (newRows: (oldRows: GridRowsProp) => GridRowsProp) => void;
setRowModesModel: (newModel: (oldModel: GridRowModesModel) => GridRowModesModel) => void;
}

function EditToolbar(props: EditToolbarProps) {
const { setRows, setRowModesModel } = props;

const handleClick = () => {
const id = randomId();
setRows((oldRows) => [
{ id, primary: '', associates: '', fields: '', key: '', video: '', isNew: true },
...oldRows
]);
setRowModesModel((oldModel) => ({
[id]: { mode: GridRowModes.Edit, fieldToFocus: 'key' },
...oldModel
}));
};

return (
<GridToolbarContainer>
<Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
Add record
</Button>
</GridToolbarContainer>
);
}

type LexiconEntriesStatus = GetAllLexEntriesQuery['lexiconAllEntries'][number] & {
isNew: boolean;
id: string;
};

export default function FullFeaturedCrudGrid() {
const [rowModesModel, setRowModesModel] = React.useState<GridRowModesModel>({});

const { data } = useGetAllLexEntriesQuery({ variables: { lexicon: '64b15233e535bc69dc95b92f' } });
const lexiconEntries = useMemo(
(): LexiconEntriesStatus[] =>
data?.lexiconAllEntries.slice(0, 10).map((entry) => ({ ...entry, id: entry.key, isNew: false })) || [],
[data]
);

//currently, rows are empty
const [rows, setRows] = React.useState<LexiconEntriesStatus[]>(lexiconEntries);

useEffect(() => {
setRows(lexiconEntries);
}, [lexiconEntries]);

useEffect(() => {
console.log(rows);
}, [rows]);

const handleRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => {
if (params.reason === GridRowEditStopReasons.rowFocusOut) {
event.defaultMuiPrevented = true;
}
};

const handleEditClick = (id: GridRowId) => () => {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
};

const handleSaveClick = (id: GridRowId) => () => {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
};

const handleDeleteClick = (id: GridRowId) => () => {
setRows(rows.filter((row) => row.id !== id));
};

const handleCancelClick = (id: GridRowId) => () => {
setRowModesModel({
...rowModesModel,
[id]: { mode: GridRowModes.View, ignoreModifications: true }
});

const editedRow = rows.find((row) => row.key === id);
if (editedRow!.isNew) {
setRows(rows.filter((row) => row.key !== id));
}
};

const processRowUpdate = (newRow: LexiconEntriesStatus) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.key === newRow.key ? updatedRow : row)));
return updatedRow;
};

const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => {
setRowModesModel(newRowModesModel);
};

const columns: GridColDef<LexiconEntriesStatus>[] = [
{
field: 'primary',
headerName: 'Primary',
width: 200,
headerAlign: 'center',
editable: true
},
{
field: 'associates',
headerName: 'Associates',
width: 200,
headerAlign: 'center',
editable: true
},
{
field: 'fields',
headerName: 'Fields',
width: 200,
headerAlign: 'center',
editable: true,
renderCell: ({ row }) => JSON.stringify(row.fields),
renderEditCell: (params) => (
<TextField
value={JSON.stringify(params.row.fields)}
onChange={(e) => {
const updatedValue = JSON.parse(e.target.value);
params.api.setEditCellValue({ id: params.id, field: params.field, value: updatedValue });
}}
variant="standard"
/>
)
},
{
field: 'key',
headerName: 'Key',
width: 200,
headerAlign: 'center',
editable: true
},
{
field: 'video',
headerName: 'Video',
width: 200,
headerAlign: 'center',
editable: true
},
{
field: 'actions',
type: 'actions',
headerName: 'Actions',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;

if (isInEditMode) {
return [
<GridActionsCellItem
icon={<SaveIcon />}
label="Save"
sx={{
color: 'primary.main'
}}
onClick={handleSaveClick(id)}
/>,
<GridActionsCellItem
icon={<CancelIcon />}
label="Cancel"
className="textPrimary"
onClick={handleCancelClick(id)}
color="inherit"
/>
];
}

return [
<GridActionsCellItem
icon={<EditIcon />}
label="Edit"
className="textPrimary"
onClick={handleEditClick(id)}
color="inherit"
/>,
<GridActionsCellItem icon={<DeleteIcon />} label="Delete" onClick={handleDeleteClick(id)} color="inherit" />
];
}
}
];

return (
<Box
sx={{
height: 500,
width: '100%',
'& .actions': {
color: 'text.secondary'
},
'& .textPrimary': {
color: 'text.primary'
}
}}
>
<DataGrid
rows={rows}
columns={columns}
editMode="row"
rowModesModel={rowModesModel}
onRowModesModelChange={handleRowModesModelChange}
onRowEditStop={handleRowEditStop}
processRowUpdate={processRowUpdate}
slots={{
toolbar: EditToolbar as GridSlots['toolbar']
}}
slotProps={{
toolbar: { setRows, setRowModesModel }
}}
/>
</Box>
);
}
7 changes: 7 additions & 0 deletions packages/admin_view/src/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ export type Query = {
_entities: Array<Maybe<_Entity>>;
_service: _Service;
lexFindAll: Array<Lexicon>;
/** Fetch all entries for a given lexicon */
lexiconAllEntries: Array<LexiconEntry>;
lexiconByKey: LexiconEntry;
lexiconSearch: Array<LexiconEntry>;
};
Expand All @@ -127,6 +129,11 @@ export type Query_EntitiesArgs = {
};


export type QueryLexiconAllEntriesArgs = {
lexicon: Scalars['String']['input'];
};


export type QueryLexiconByKeyArgs = {
key: Scalars['String']['input'];
lexicon: Scalars['String']['input'];
Expand Down
Loading
Loading