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

CRUD component #4486

Open
wants to merge 77 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
aa8a72c
Allow full-size content in PageContainer
apedroferreira Nov 26, 2024
b5d7544
CRUS list component (WIP)
apedroferreira Nov 27, 2024
7d63c71
First version of List without documentation
apedroferreira Nov 27, 2024
35f63a3
Show component (initial version)
apedroferreira Dec 2, 2024
0bb5aa1
Better Show component
apedroferreira Dec 11, 2024
dccbb8e
Create component - saving progress
apedroferreira Jan 16, 2025
6b47e14
Let's pop a save
apedroferreira Jan 22, 2025
e807873
Merge remote-tracking branch 'upstream/master' into crud-list
apedroferreira Jan 22, 2025
59d923d
Fix merge
apedroferreira Jan 22, 2025
87bd739
Fix merge more
apedroferreira Jan 22, 2025
f7a894a
More fixes and updates
apedroferreira Jan 22, 2025
355be7a
Render correct form field types
apedroferreira Jan 22, 2025
c650f1a
Form integration logic, in progress
apedroferreira Jan 29, 2025
5eaa530
Finish basic form logic
apedroferreira Jan 30, 2025
0575330
Refactor forms not to use any library, add validation feature
apedroferreira Jan 30, 2025
cb4fc26
Improve form state and field validation
apedroferreira Jan 30, 2025
fcbb266
Make form pages reusable, use it for Create page
apedroferreira Jan 30, 2025
07f4923
Add Edit component and page
apedroferreira Jan 31, 2025
78433dc
CRUD context provider - in progress
apedroferreira Jan 31, 2025
734d716
Typing changes
apedroferreira Feb 3, 2025
3784767
All-in-one CRUD ohyeah
apedroferreira Feb 3, 2025
6146dd1
Improvements
apedroferreira Feb 3, 2025
47732fe
More fixes
apedroferreira Feb 3, 2025
6ab92c2
Delete flow and error fixes
apedroferreira Feb 3, 2025
21db37d
Clean up example
apedroferreira Feb 3, 2025
ed873ad
Date field fixes
apedroferreira Feb 4, 2025
9d149eb
Improve field display formatting in Show pages
apedroferreira Feb 4, 2025
4c87878
Reorder imports
apedroferreira Feb 4, 2025
09ed0ed
Merge remote-tracking branch 'upstream/master' into crud-list
apedroferreira Feb 4, 2025
46454cd
API docs
apedroferreira Feb 5, 2025
84178e1
Add deep linking in List + some features
apedroferreira Feb 5, 2025
0a8ae4b
Fixes to URL table state
apedroferreira Feb 5, 2025
da9f8db
Many improvements and fixes + initial demo in docs
apedroferreira Feb 6, 2025
461b973
Better demo + fixes
apedroferreira Feb 7, 2025
3cf7eb8
Make dialogs work in iframes
apedroferreira Feb 7, 2025
fd8091a
Add data source section
apedroferreira Feb 7, 2025
61e7510
Guide for basic docs page structure
apedroferreira Feb 7, 2025
bdf7edc
Write docs a bit more, add new 'longString' field type
apedroferreira Feb 11, 2025
213b883
Document form validation
apedroferreira Feb 11, 2025
44054cf
Better list rows, shorter loading times in demo
apedroferreira Feb 11, 2025
92929f0
Fix yup adpater example
apedroferreira Feb 11, 2025
5997263
Document advanced usage + List component, almost there
apedroferreira Feb 12, 2025
e67a3e3
Finish docs for now
apedroferreira Feb 13, 2025
fc4fa14
Next.js playground, once all bugs are fixes we can send the PR for re…
apedroferreira Feb 13, 2025
2521d77
Next.js pages router playground + final bug fixes before review
apedroferreira Feb 14, 2025
fadcdec
Additional fix for Next.js pages router playground
apedroferreira Feb 14, 2025
3b62551
Merge remote-tracking branch 'upstream/master' into crud-list
apedroferreira Feb 14, 2025
357a0db
Install and dedupe after merge
apedroferreira Feb 14, 2025
4dd23b1
Form reset improvement
apedroferreira Feb 14, 2025
146dd1d
Ongoing self-review
apedroferreira Feb 14, 2025
f6931b4
More self-review
apedroferreira Feb 14, 2025
0d05f85
Temp rename Crud to force case change
apedroferreira Feb 14, 2025
4bb18c4
Restore correct case for Crud
apedroferreira Feb 14, 2025
80ceee9
Removed unwanted CRUD folder
apedroferreira Feb 14, 2025
fa70ed2
Temporary rename
apedroferreira Feb 17, 2025
a416385
Restore original names
apedroferreira Feb 17, 2025
243b8c6
Dedupe + rename FormPage
apedroferreira Feb 17, 2025
db59965
boy oh boy
apedroferreira Feb 17, 2025
c42bd20
fix more things
apedroferreira Feb 17, 2025
fe6950d
More agnostic form component
apedroferreira Feb 17, 2025
18ef629
Document proptypes for CRUD form
apedroferreira Feb 17, 2025
16ddbe1
Better types
apedroferreira Feb 17, 2025
ad97fc1
spacing
apedroferreira Feb 17, 2025
b2d4f5e
Organize imports
apedroferreira Feb 17, 2025
2c273c8
Better create demo
apedroferreira Feb 17, 2025
5727b45
i fix i fix
apedroferreira Feb 17, 2025
27a7175
smol improvements
apedroferreira Feb 17, 2025
41c5fbb
Add caching based on MUI X, will test it later
apedroferreira Feb 19, 2025
66a50ab
Caching is working great, just need to reupdate docs with it
apedroferreira Feb 20, 2025
1e7bc4d
Document caching, add option to disable cache
apedroferreira Feb 21, 2025
d586f8a
Merge remote-tracking branch 'upstream/master' into crud-list
apedroferreira Feb 21, 2025
92a4e8a
Clear cache on list refresh
apedroferreira Feb 21, 2025
d01d8de
Fix demo with pro data grid
apedroferreira Feb 21, 2025
057ce7d
Fix data grid versions
apedroferreira Feb 21, 2025
758798e
Write caching better
apedroferreira Feb 21, 2025
9f44235
Bharat feedback and some docs improvements
apedroferreira Feb 25, 2025
1e21054
Merge remote-tracking branch 'upstream/master' into crud-list
apedroferreira Feb 25, 2025
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
3 changes: 2 additions & 1 deletion docs/data/toolpad/core/all-components/all-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

<p class="description">This page contains an index to the components included in Toolpad Core.</p>

- [Account](/toolpad/core/react-account/)
- [App Provider](/toolpad/core/react-app-provider/)
- [Crud](/toolpad/core/react-crud/)
- [Dashboard Layout](/toolpad/core/react-dashboard-layout/)
- [Page Container](/toolpad/core/react-page-container/)
- [Sign-in Page](/toolpad/core/react-sign-in-page/)
- [Account](/toolpad/core/react-account/)
15 changes: 0 additions & 15 deletions docs/data/toolpad/core/components/crud-page/crud-page.md

This file was deleted.

317 changes: 317 additions & 0 deletions docs/data/toolpad/core/components/crud/CrudAdvanced.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { createTheme } from '@mui/material/styles';
import StickyNote2Icon from '@mui/icons-material/StickyNote2';
import { AppProvider } from '@toolpad/core/AppProvider';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import { PageContainer } from '@toolpad/core/PageContainer';
import {
Create,
CrudProvider,
DataSourceCache,
Edit,
List,
Show,
} from '@toolpad/core/Crud';
import { useDemoRouter } from '@toolpad/core/internal';

const NAVIGATION = [
{
segment: 'notes',
title: 'Notes',
icon: <StickyNote2Icon />,
pattern: 'notes{/:noteId}*',
},
];

const demoTheme = createTheme({
cssVariables: {
colorSchemeSelector: 'data-toolpad-color-scheme',
},
colorSchemes: { light: true, dark: true },
breakpoints: {
values: {
xs: 0,
sm: 600,
md: 600,
lg: 1200,
xl: 1536,
},
},
});

let notesStore = [
{ id: 1, title: 'Grocery List Item', text: 'Buy more coffee.' },
{ id: 2, title: 'Personal Goal', text: 'Finish reading the book.' },
];

export const notesDataSource = {
fields: [
{ field: 'id', headerName: 'ID' },
{ field: 'title', headerName: 'Title', flex: 1 },
{ field: 'text', headerName: 'Text', type: 'longString', flex: 1 },
],
getMany: async ({ paginationModel, filterModel, sortModel }) => {
return new Promise((resolve) => {
setTimeout(() => {
let processedNotes = [...notesStore];

// Apply filters (demo only)
if (filterModel?.items?.length) {
filterModel.items.forEach(({ field, value, operator }) => {
if (!field || value == null) {
return;
}

processedNotes = processedNotes.filter((note) => {
const noteValue = note[field];

switch (operator) {
case 'contains':
return String(noteValue)
.toLowerCase()
.includes(String(value).toLowerCase());
case 'equals':
return noteValue === value;
case 'startsWith':
return String(noteValue)
.toLowerCase()
.startsWith(String(value).toLowerCase());
case 'endsWith':
return String(noteValue)
.toLowerCase()
.endsWith(String(value).toLowerCase());
case '>':
return noteValue > value;
case '<':
return noteValue < value;
default:
return true;
}
});
});
}

// Apply sorting
if (sortModel?.length) {
processedNotes.sort((a, b) => {
for (const { field, sort } of sortModel) {
if (a[field] < b[field]) {
return sort === 'asc' ? -1 : 1;
}
if (a[field] > b[field]) {
return sort === 'asc' ? 1 : -1;
}
}
return 0;
});
}

// Apply pagination
const start = paginationModel.page * paginationModel.pageSize;
const end = start + paginationModel.pageSize;
const paginatedNotes = processedNotes.slice(start, end);

resolve({
items: paginatedNotes,
itemCount: processedNotes.length,
});
}, 750);
});
},
getOne: (noteId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const noteToShow = notesStore.find((note) => note.id === Number(noteId));

if (noteToShow) {
resolve(noteToShow);
} else {
reject(new Error('Note not found'));
}
}, 750);
});
},
createOne: (data) => {
return new Promise((resolve) => {
setTimeout(() => {
const newNote = { id: notesStore.length + 1, ...data };

notesStore = [...notesStore, newNote];

resolve(newNote);
}, 750);
});
},
updateOne: (noteId, data) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
let updatedNote = null;

notesStore = notesStore.map((note) => {
if (note.id === Number(noteId)) {
updatedNote = { ...note, ...data };
return updatedNote;
}
return note;
});

if (updatedNote) {
resolve(updatedNote);
} else {
reject(new Error('Note not found'));
}
}, 750);
});
},
deleteOne: (noteId) => {
return new Promise((resolve) => {
setTimeout(() => {
notesStore = notesStore.filter((note) => note.id !== Number(noteId));

resolve();
}, 750);
});
},
validate: (formValues) => {
const errors = {};

if (!formValues.title) {
errors.title = 'Title is required';
}
if (formValues.title && formValues.title.length < 3) {
errors.title = 'Title must be at least 3 characters long';
}
if (!formValues.text) {
errors.text = 'Text is required';
}

return errors;
},
};

const notesCache = new DataSourceCache();

function matchPath(pattern, pathname) {
const regex = new RegExp(`^${pattern.replace(/:[^/]+/g, '([^/]+)')}$`);
const match = pathname.match(regex);
return match ? match[1] : null;
}

function CrudAdvanced(props) {
const { window } = props;

// Remove this const when copying and pasting into your project.
const demoWindow = window !== undefined ? window() : undefined;

const rootPath = '/notes';

const router = useDemoRouter(rootPath);

const listPath = rootPath;
const showPath = `${rootPath}/:noteId`;
const createPath = `${rootPath}/new`;
const editPath = `${rootPath}/:noteId/edit`;

const title = React.useMemo(() => {
if (router.pathname === createPath) {
return 'New Note';
}
const editNoteId = matchPath(editPath, router.pathname);
if (editNoteId) {
return `Note ${editNoteId} - Edit`;
}
const showNoteId = matchPath(showPath, router.pathname);
if (showNoteId) {
return `Note ${showNoteId}`;
}

return undefined;
}, [createPath, editPath, router.pathname, showPath]);

const handleRowClick = React.useCallback(
(noteId) => {
router.navigate(`${rootPath}/${String(noteId)}`);
},
[router],
);

const handleCreateClick = React.useCallback(() => {
router.navigate(createPath);
}, [createPath, router]);

const handleEditClick = React.useCallback(
(noteId) => {
router.navigate(`${rootPath}/${String(noteId)}/edit`);
},
[router],
);

const handleCreate = React.useCallback(() => {
router.navigate(listPath);
}, [listPath, router]);

const handleEdit = React.useCallback(() => {
router.navigate(listPath);
}, [listPath, router]);

const handleDelete = React.useCallback(() => {
router.navigate(listPath);
}, [listPath, router]);

const showNoteId = matchPath(showPath, router.pathname);
const editNoteId = matchPath(editPath, router.pathname);

return (
<AppProvider
navigation={NAVIGATION}
router={router}
theme={demoTheme}
window={demoWindow}
>
<DashboardLayout defaultSidebarCollapsed>
<PageContainer title={title}>
{/* preview-start */}
<CrudProvider dataSource={notesDataSource} dataSourceCache={notesCache}>
{router.pathname === listPath ? (
<List
initialPageSize={10}
onRowClick={handleRowClick}
onCreateClick={handleCreateClick}
onEditClick={handleEditClick}
/>
) : null}
{router.pathname === createPath ? (
<Create
initialValues={{ title: 'New note' }}
onSubmitSuccess={handleCreate}
resetOnSubmit={false}
/>
) : null}
{router.pathname !== createPath && showNoteId ? (
<Show
id={showNoteId}
onEditClick={handleEditClick}
onDelete={handleDelete}
/>
) : null}
{editNoteId ? (
<Edit id={editNoteId} onSubmitSuccess={handleEdit} />
) : null}
</CrudProvider>
{/* preview-end */}
</PageContainer>
</DashboardLayout>
</AppProvider>
);
}

CrudAdvanced.propTypes = {
/**
* Injected by the documentation to work in an iframe.
* Remove this when copying and pasting into your project.
*/
window: PropTypes.func,
};

export default CrudAdvanced;
Loading
Loading