Version 3.0
This commit is contained in:
parent
deb60c112f
commit
805ff8d249
index.html
node_modules
package-lock.jsonpublic
src.zipsrc
|
@ -20,5 +20,5 @@
|
|||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</html>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</html>
|
||||
|
|
|
@ -2226,9 +2226,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.17.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.27.tgz",
|
||||
"integrity": "sha512-U58sbKhDrthHlxHRJw7ZLiLDZGmAUOZUbpw0S6nL27sYUdhvgBLCRu/keSd6qcTsfArd1sRFCCBxzWATGr/0UA==",
|
||||
"version": "20.17.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.28.tgz",
|
||||
"integrity": "sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -2271,9 +2271,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
||||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz",
|
||||
"integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -3400,9 +3400,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.124",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.124.tgz",
|
||||
"integrity": "sha512-riELkpDUqBi00gqreV3RIGoowxGrfueEKBd6zPdOk/I8lvuFpBGNkYoHof3zUHbiTBsIU8oxdIIL/WNrAG1/7A==",
|
||||
"version": "1.5.125",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.125.tgz",
|
||||
"integrity": "sha512-A2+qEsSUc95QvyFDl7PNwkDDNphIKBVfBBtWWkPGRbiWEgzLo0SvLygYF6HgzVduHd+4WGPB/WD64POFgwzY3g==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
{
|
||||
"hash": "536f3c70",
|
||||
"configHash": "4b3d8f8b",
|
||||
"lockfileHash": "0b0e3dce",
|
||||
"browserHash": "7f27dcc2",
|
||||
"hash": "55f22dac",
|
||||
"configHash": "84ec339b",
|
||||
"lockfileHash": "af9926ca",
|
||||
"browserHash": "a7ac9476",
|
||||
"optimized": {
|
||||
"react": {
|
||||
"src": "../../react/index.js",
|
||||
"file": "react.js",
|
||||
"fileHash": "8db10fd1",
|
||||
"fileHash": "e2a594ff",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react-dom": {
|
||||
"src": "../../react-dom/index.js",
|
||||
"file": "react-dom.js",
|
||||
"fileHash": "5df02561",
|
||||
"fileHash": "df0f9071",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react/jsx-dev-runtime": {
|
||||
"src": "../../react/jsx-dev-runtime.js",
|
||||
"file": "react_jsx-dev-runtime.js",
|
||||
"fileHash": "454d74b2",
|
||||
"fileHash": "01dfc401",
|
||||
"needsInterop": true
|
||||
},
|
||||
"react/jsx-runtime": {
|
||||
"src": "../../react/jsx-runtime.js",
|
||||
"file": "react_jsx-runtime.js",
|
||||
"fileHash": "f9fb0493",
|
||||
"fileHash": "9b053a7e",
|
||||
"needsInterop": true
|
||||
},
|
||||
"clsx": {
|
||||
"src": "../../clsx/dist/clsx.mjs",
|
||||
"file": "clsx.js",
|
||||
"fileHash": "0acaaaf7",
|
||||
"fileHash": "adf18f86",
|
||||
"needsInterop": false
|
||||
},
|
||||
"localforage": {
|
||||
"src": "../../localforage/dist/localforage.js",
|
||||
"file": "localforage.js",
|
||||
"fileHash": "e76b88f1",
|
||||
"fileHash": "48f676fe",
|
||||
"needsInterop": true
|
||||
},
|
||||
"ra-core": {
|
||||
"src": "../../ra-core/dist/esm/index.js",
|
||||
"file": "ra-core.js",
|
||||
"fileHash": "6a13df7d",
|
||||
"fileHash": "2a3b3c22",
|
||||
"needsInterop": false
|
||||
},
|
||||
"ra-data-local-forage": {
|
||||
"src": "../../ra-data-local-forage/dist/esm/index.js",
|
||||
"file": "ra-data-local-forage.js",
|
||||
"fileHash": "cd41ed79",
|
||||
"fileHash": "876197e0",
|
||||
"needsInterop": false
|
||||
},
|
||||
"react-dom/client": {
|
||||
"src": "../../react-dom/client.js",
|
||||
"file": "react-dom_client.js",
|
||||
"fileHash": "b0b6a4aa",
|
||||
"fileHash": "2334656b",
|
||||
"needsInterop": true
|
||||
}
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@ This package contains type definitions for node (https://nodejs.org/).
|
|||
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node/v20.
|
||||
|
||||
### Additional Details
|
||||
* Last updated: Mon, 24 Mar 2025 11:02:21 GMT
|
||||
* Last updated: Thu, 27 Mar 2025 03:13:14 GMT
|
||||
* Dependencies: [undici-types](https://npmjs.com/package/undici-types)
|
||||
|
||||
# Credits
|
||||
|
|
|
@ -2542,6 +2542,17 @@ declare module "fs" {
|
|||
options: ReadAsyncOptions<TBuffer>,
|
||||
callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: TBuffer) => void,
|
||||
): void;
|
||||
export function read<TBuffer extends NodeJS.ArrayBufferView>(
|
||||
fd: number,
|
||||
buffer: TBuffer,
|
||||
options: ReadSyncOptions,
|
||||
callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: TBuffer) => void,
|
||||
): void;
|
||||
export function read<TBuffer extends NodeJS.ArrayBufferView>(
|
||||
fd: number,
|
||||
buffer: TBuffer,
|
||||
callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: TBuffer) => void,
|
||||
): void;
|
||||
export function read(
|
||||
fd: number,
|
||||
callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: NodeJS.ArrayBufferView) => void,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@types/node",
|
||||
"version": "20.17.27",
|
||||
"version": "20.17.28",
|
||||
"description": "TypeScript definitions for node",
|
||||
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node",
|
||||
"license": "MIT",
|
||||
|
@ -215,6 +215,6 @@
|
|||
"undici-types": "~6.19.2"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"typesPublisherContentHash": "c377d89f9114d74271a41a8ef892c2e9ec1a56021dd48a1abf8ab139f220fd12",
|
||||
"typesPublisherContentHash": "0e6e31fc35f69eb67f9b49e14e070a90616484c787f902edc4ef687f2777f872",
|
||||
"typeScriptVersion": "5.0"
|
||||
}
|
|
@ -8,7 +8,7 @@ This package contains type definitions for semver (https://github.com/npm/node-s
|
|||
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/semver.
|
||||
|
||||
### Additional Details
|
||||
* Last updated: Sat, 24 Feb 2024 16:35:28 GMT
|
||||
* Last updated: Wed, 26 Mar 2025 16:02:25 GMT
|
||||
* Dependencies: none
|
||||
|
||||
# Credits
|
||||
|
|
|
@ -101,7 +101,7 @@ export const SEMVER_SPEC_VERSION: "2.0.0";
|
|||
|
||||
export const RELEASE_TYPES: ReleaseType[];
|
||||
|
||||
export type ReleaseType = "major" | "premajor" | "minor" | "preminor" | "patch" | "prepatch" | "prerelease";
|
||||
export type ReleaseType = "major" | "premajor" | "minor" | "preminor" | "patch" | "prepatch" | "prerelease" | "release";
|
||||
|
||||
export interface Options {
|
||||
loose?: boolean | undefined;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@types/semver",
|
||||
"version": "7.5.8",
|
||||
"version": "7.7.0",
|
||||
"description": "TypeScript definitions for semver",
|
||||
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/semver",
|
||||
"license": "MIT",
|
||||
|
@ -45,6 +45,7 @@
|
|||
},
|
||||
"scripts": {},
|
||||
"dependencies": {},
|
||||
"typesPublisherContentHash": "eeb34e966b621b4e47b2b11e63847d881e897b4ef9ec7a909c8d3730f7f3d6f8",
|
||||
"typeScriptVersion": "4.6"
|
||||
"peerDependencies": {},
|
||||
"typesPublisherContentHash": "2686be620a3b4ba9c210ad83cb60a74eefd9e714cd5942d2ff2f24993ad4b9ec",
|
||||
"typeScriptVersion": "5.0"
|
||||
}
|
|
@ -2203,7 +2203,8 @@ module.exports = {
|
|||
"33.4.3",
|
||||
"33.4.4",
|
||||
"33.4.5",
|
||||
"33.4.6"
|
||||
"33.4.6",
|
||||
"33.4.7"
|
||||
],
|
||||
"131.0.6776.0": [
|
||||
"34.0.0-alpha.1"
|
||||
|
@ -2269,7 +2270,8 @@ module.exports = {
|
|||
"34.3.1",
|
||||
"34.3.2",
|
||||
"34.3.3",
|
||||
"34.3.4"
|
||||
"34.3.4",
|
||||
"34.4.0"
|
||||
],
|
||||
"133.0.6920.0": [
|
||||
"35.0.0-alpha.1",
|
||||
|
@ -2310,7 +2312,8 @@ module.exports = {
|
|||
"35.0.3"
|
||||
],
|
||||
"134.0.6998.165": [
|
||||
"35.1.0"
|
||||
"35.1.0",
|
||||
"35.1.1"
|
||||
],
|
||||
"135.0.7049.5": [
|
||||
"36.0.0-alpha.1"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1439,6 +1439,7 @@ module.exports = {
|
|||
"33.4.4": "130.0.6723.191",
|
||||
"33.4.5": "130.0.6723.191",
|
||||
"33.4.6": "130.0.6723.191",
|
||||
"33.4.7": "130.0.6723.191",
|
||||
"34.0.0-alpha.1": "131.0.6776.0",
|
||||
"34.0.0-alpha.2": "132.0.6779.0",
|
||||
"34.0.0-alpha.3": "132.0.6789.1",
|
||||
|
@ -1475,6 +1476,7 @@ module.exports = {
|
|||
"34.3.2": "132.0.6834.210",
|
||||
"34.3.3": "132.0.6834.210",
|
||||
"34.3.4": "132.0.6834.210",
|
||||
"34.4.0": "132.0.6834.210",
|
||||
"35.0.0-alpha.1": "133.0.6920.0",
|
||||
"35.0.0-alpha.2": "133.0.6920.0",
|
||||
"35.0.0-alpha.3": "133.0.6920.0",
|
||||
|
@ -1498,6 +1500,7 @@ module.exports = {
|
|||
"35.0.2": "134.0.6998.88",
|
||||
"35.0.3": "134.0.6998.88",
|
||||
"35.1.0": "134.0.6998.165",
|
||||
"35.1.1": "134.0.6998.165",
|
||||
"36.0.0-alpha.1": "135.0.7049.5",
|
||||
"36.0.0-alpha.2": "136.0.7062.0",
|
||||
"36.0.0-alpha.3": "136.0.7062.0",
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "electron-to-chromium",
|
||||
"version": "1.5.124",
|
||||
"version": "1.5.125",
|
||||
"description": "Provides a list of electron-to-chromium version mappings",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
|
|
|
@ -178,6 +178,7 @@ module.exports = {
|
|||
"34.1": "132",
|
||||
"34.2": "132",
|
||||
"34.3": "132",
|
||||
"34.4": "132",
|
||||
"35.0": "134",
|
||||
"35.1": "134",
|
||||
"36.0": "136"
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"0.20":"39","0.21":"41","0.22":"41","0.23":"41","0.24":"41","0.25":"42","0.26":"42","0.27":"43","0.28":"43","0.29":"43","0.30":"44","0.31":"45","0.32":"45","0.33":"45","0.34":"45","0.35":"45","0.36":"47","0.37":"49","1.0":"49","1.1":"50","1.2":"51","1.3":"52","1.4":"53","1.5":"54","1.6":"56","1.7":"58","1.8":"59","2.0":"61","2.1":"61","3.0":"66","3.1":"66","4.0":"69","4.1":"69","4.2":"69","5.0":"73","6.0":"76","6.1":"76","7.0":"78","7.1":"78","7.2":"78","7.3":"78","8.0":"80","8.1":"80","8.2":"80","8.3":"80","8.4":"80","8.5":"80","9.0":"83","9.1":"83","9.2":"83","9.3":"83","9.4":"83","10.0":"85","10.1":"85","10.2":"85","10.3":"85","10.4":"85","11.0":"87","11.1":"87","11.2":"87","11.3":"87","11.4":"87","11.5":"87","12.0":"89","12.1":"89","12.2":"89","13.0":"91","13.1":"91","13.2":"91","13.3":"91","13.4":"91","13.5":"91","13.6":"91","14.0":"93","14.1":"93","14.2":"93","15.0":"94","15.1":"94","15.2":"94","15.3":"94","15.4":"94","15.5":"94","16.0":"96","16.1":"96","16.2":"96","17.0":"98","17.1":"98","17.2":"98","17.3":"98","17.4":"98","18.0":"100","18.1":"100","18.2":"100","18.3":"100","19.0":"102","19.1":"102","20.0":"104","20.1":"104","20.2":"104","20.3":"104","21.0":"106","21.1":"106","21.2":"106","21.3":"106","21.4":"106","22.0":"108","22.1":"108","22.2":"108","22.3":"108","23.0":"110","23.1":"110","23.2":"110","23.3":"110","24.0":"112","24.1":"112","24.2":"112","24.3":"112","24.4":"112","24.5":"112","24.6":"112","24.7":"112","24.8":"112","25.0":"114","25.1":"114","25.2":"114","25.3":"114","25.4":"114","25.5":"114","25.6":"114","25.7":"114","25.8":"114","25.9":"114","26.0":"116","26.1":"116","26.2":"116","26.3":"116","26.4":"116","26.5":"116","26.6":"116","27.0":"118","27.1":"118","27.2":"118","27.3":"118","28.0":"120","28.1":"120","28.2":"120","28.3":"120","29.0":"122","29.1":"122","29.2":"122","29.3":"122","29.4":"122","30.0":"124","30.1":"124","30.2":"124","30.3":"124","30.4":"124","30.5":"124","31.0":"126","31.1":"126","31.2":"126","31.3":"126","31.4":"126","31.5":"126","31.6":"126","31.7":"126","32.0":"128","32.1":"128","32.2":"128","32.3":"128","33.0":"130","33.1":"130","33.2":"130","33.3":"130","33.4":"130","34.0":"132","34.1":"132","34.2":"132","34.3":"132","35.0":"134","35.1":"134","36.0":"136"}
|
||||
{"0.20":"39","0.21":"41","0.22":"41","0.23":"41","0.24":"41","0.25":"42","0.26":"42","0.27":"43","0.28":"43","0.29":"43","0.30":"44","0.31":"45","0.32":"45","0.33":"45","0.34":"45","0.35":"45","0.36":"47","0.37":"49","1.0":"49","1.1":"50","1.2":"51","1.3":"52","1.4":"53","1.5":"54","1.6":"56","1.7":"58","1.8":"59","2.0":"61","2.1":"61","3.0":"66","3.1":"66","4.0":"69","4.1":"69","4.2":"69","5.0":"73","6.0":"76","6.1":"76","7.0":"78","7.1":"78","7.2":"78","7.3":"78","8.0":"80","8.1":"80","8.2":"80","8.3":"80","8.4":"80","8.5":"80","9.0":"83","9.1":"83","9.2":"83","9.3":"83","9.4":"83","10.0":"85","10.1":"85","10.2":"85","10.3":"85","10.4":"85","11.0":"87","11.1":"87","11.2":"87","11.3":"87","11.4":"87","11.5":"87","12.0":"89","12.1":"89","12.2":"89","13.0":"91","13.1":"91","13.2":"91","13.3":"91","13.4":"91","13.5":"91","13.6":"91","14.0":"93","14.1":"93","14.2":"93","15.0":"94","15.1":"94","15.2":"94","15.3":"94","15.4":"94","15.5":"94","16.0":"96","16.1":"96","16.2":"96","17.0":"98","17.1":"98","17.2":"98","17.3":"98","17.4":"98","18.0":"100","18.1":"100","18.2":"100","18.3":"100","19.0":"102","19.1":"102","20.0":"104","20.1":"104","20.2":"104","20.3":"104","21.0":"106","21.1":"106","21.2":"106","21.3":"106","21.4":"106","22.0":"108","22.1":"108","22.2":"108","22.3":"108","23.0":"110","23.1":"110","23.2":"110","23.3":"110","24.0":"112","24.1":"112","24.2":"112","24.3":"112","24.4":"112","24.5":"112","24.6":"112","24.7":"112","24.8":"112","25.0":"114","25.1":"114","25.2":"114","25.3":"114","25.4":"114","25.5":"114","25.6":"114","25.7":"114","25.8":"114","25.9":"114","26.0":"116","26.1":"116","26.2":"116","26.3":"116","26.4":"116","26.5":"116","26.6":"116","27.0":"118","27.1":"118","27.2":"118","27.3":"118","28.0":"120","28.1":"120","28.2":"120","28.3":"120","29.0":"122","29.1":"122","29.2":"122","29.3":"122","29.4":"122","30.0":"124","30.1":"124","30.2":"124","30.3":"124","30.4":"124","30.5":"124","31.0":"126","31.1":"126","31.2":"126","31.3":"126","31.4":"126","31.5":"126","31.6":"126","31.7":"126","32.0":"128","32.1":"128","32.2":"128","32.3":"128","33.0":"130","33.1":"130","33.2":"130","33.3":"130","33.4":"130","34.0":"132","34.1":"132","34.2":"132","34.3":"132","34.4":"132","35.0":"134","35.1":"134","36.0":"136"}
|
|
@ -2931,9 +2931,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.17.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.27.tgz",
|
||||
"integrity": "sha512-U58sbKhDrthHlxHRJw7ZLiLDZGmAUOZUbpw0S6nL27sYUdhvgBLCRu/keSd6qcTsfArd1sRFCCBxzWATGr/0UA==",
|
||||
"version": "20.17.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.28.tgz",
|
||||
"integrity": "sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -2976,9 +2976,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
||||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz",
|
||||
"integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@ -4105,9 +4105,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.124",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.124.tgz",
|
||||
"integrity": "sha512-riELkpDUqBi00gqreV3RIGoowxGrfueEKBd6zPdOk/I8lvuFpBGNkYoHof3zUHbiTBsIU8oxdIIL/WNrAG1/7A==",
|
||||
"version": "1.5.125",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.125.tgz",
|
||||
"integrity": "sha512-A2+qEsSUc95QvyFDl7PNwkDDNphIKBVfBBtWWkPGRbiWEgzLo0SvLygYF6HgzVduHd+4WGPB/WD64POFgwzY3g==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ToDo App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
80
src/App.tsx
80
src/App.tsx
|
@ -1,11 +1,73 @@
|
|||
import { CoreAdmin, Resource } from "ra-core";
|
||||
|
||||
import { Layout } from "./Layout";
|
||||
import { dataProvider } from "./dataProvider";
|
||||
import { TodoList } from "./TodoList";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Header from './Header';
|
||||
import { Footer } from './Footer';
|
||||
import TodoList from './TodoList';
|
||||
import Sidebar from './Sidebar';
|
||||
import { Todo } from './types';
|
||||
import './app.css';
|
||||
|
||||
export const App = () => (
|
||||
<CoreAdmin layout={Layout} dataProvider={dataProvider}>
|
||||
<Resource name="todos" list={TodoList} />
|
||||
</CoreAdmin>
|
||||
);
|
||||
const App = () => {
|
||||
const [todos, setTodos] = useState<Todo[]>(() => {
|
||||
const saved = localStorage.getItem("todos");
|
||||
return saved ? JSON.parse(saved) : [];
|
||||
});
|
||||
|
||||
const [categories, setCategories] = useState<string[]>(() => {
|
||||
const saved = localStorage.getItem("categories");
|
||||
return saved ? JSON.parse(saved) : [];
|
||||
});
|
||||
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("todos", JSON.stringify(todos));
|
||||
}, [todos]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("categories", JSON.stringify(categories));
|
||||
}, [categories]);
|
||||
|
||||
const handleAddTodo = (todo: Todo) => {
|
||||
setTodos([...todos, todo]);
|
||||
};
|
||||
|
||||
const handleToggle = (id: number) => {
|
||||
setTodos(
|
||||
todos.map((todo) =>
|
||||
todo.id === id ? { ...todo, completed: !todo.completed } : todo
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handleDelete = (id: number) => {
|
||||
setTodos(todos.filter((todo) => todo.id !== id));
|
||||
};
|
||||
|
||||
const handleAddCategory = (cat: string) => {
|
||||
setCategories([...categories, cat]);
|
||||
};
|
||||
|
||||
const filteredTodos = todos.filter(
|
||||
(todo) => !selectedCategory || todo.category === selectedCategory
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<Sidebar
|
||||
categories={categories}
|
||||
selectedCategory={selectedCategory}
|
||||
onSelectCategory={setSelectedCategory}
|
||||
onAddCategory={handleAddCategory}
|
||||
/>
|
||||
<div className="main">
|
||||
<h1 className="title">todos</h1>
|
||||
<Header onAddTodo={handleAddTodo} categories={categories} />
|
||||
<TodoList todos={filteredTodos} onToggle={handleToggle} onDelete={handleDelete} />
|
||||
<Footer count={filteredTodos.length} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
|
136
src/Footer.tsx
136
src/Footer.tsx
|
@ -1,134 +1,10 @@
|
|||
import { useListContext, useDeleteMany } from "ra-core";
|
||||
import clsx from "clsx";
|
||||
import type { Todo } from "./types";
|
||||
|
||||
export const Footer = () => {
|
||||
const { data, total, filterValues, setFilters, isPending, error } =
|
||||
useListContext<Todo>();
|
||||
const [deleteMany] = useDeleteMany();
|
||||
import React from 'react';
|
||||
|
||||
if (isPending || error) return null;
|
||||
interface FooterProps {
|
||||
count: number;
|
||||
}
|
||||
|
||||
const handleClearCompleted = () => {
|
||||
const completedIds = data
|
||||
.filter((todo) => todo.completed)
|
||||
.map((todo) => todo.id);
|
||||
deleteMany("todos", { ids: completedIds });
|
||||
};
|
||||
|
||||
const handlePriorityFilter = (priority: string) => {
|
||||
setFilters({ ...filterValues, priority });
|
||||
};
|
||||
|
||||
const clearPriorityFilter = () => {
|
||||
const { priority, ...rest } = filterValues;
|
||||
setFilters(rest);
|
||||
};
|
||||
|
||||
return (
|
||||
<footer className="footer">
|
||||
<span className="todo-count">
|
||||
<strong>{total}</strong>
|
||||
{total === 1 ? " item" : " items"} left
|
||||
</span>
|
||||
|
||||
<ul className="filters">
|
||||
{/* Filtro por completado */}
|
||||
<li>
|
||||
<a
|
||||
className={clsx({
|
||||
selected: !("completed" in filterValues),
|
||||
})}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
setFilters({});
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
All
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className={clsx({ selected: filterValues.completed === false })}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
setFilters({ ...filterValues, completed: false });
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
Active
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className={clsx({ selected: filterValues.completed === true })}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
setFilters({ ...filterValues, completed: true });
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
Completed
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{/* Filtro por prioridad */}
|
||||
<li>
|
||||
<a
|
||||
className={clsx({ selected: filterValues.priority === "alta" })}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
handlePriorityFilter("alta");
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
Alta
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className={clsx({ selected: filterValues.priority === "media" })}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
handlePriorityFilter("media");
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
Media
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className={clsx({ selected: filterValues.priority === "baja" })}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
handlePriorityFilter("baja");
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
Baja
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
className={clsx({ selected: !filterValues.priority })}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
clearPriorityFilter();
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
Todas las prioridades
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{total != null && (
|
||||
<button className="clear-completed" onClick={handleClearCompleted}>
|
||||
Clear completed
|
||||
</button>
|
||||
)}
|
||||
</footer>
|
||||
);
|
||||
export const Footer: React.FC<FooterProps> = ({ count }) => {
|
||||
return <footer>{count} item(s) left</footer>;
|
||||
};
|
||||
|
|
|
@ -1,44 +1,53 @@
|
|||
import { useCreate } from "ra-core";
|
||||
import { useState } from "react";
|
||||
|
||||
export const Header = () => {
|
||||
const [create] = useCreate();
|
||||
const [title, setTitle] = useState("");
|
||||
const [priority, setPriority] = useState("media");
|
||||
import React, { useState } from 'react';
|
||||
import { Todo } from './types';
|
||||
|
||||
const handleSubmit = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key !== "Enter" || !title.trim()) return;
|
||||
interface HeaderProps {
|
||||
onAddTodo: (todo: Todo) => void;
|
||||
categories: string[];
|
||||
}
|
||||
|
||||
create("todos", {
|
||||
data: {
|
||||
title,
|
||||
const Header: React.FC<HeaderProps> = ({ onAddTodo, categories }) => {
|
||||
const [title, setTitle] = useState('');
|
||||
const [priority, setPriority] = useState<'alta' | 'media' | 'baja'>('media');
|
||||
const [category, setCategory] = useState<string>('');
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter' && title.trim() !== '') {
|
||||
onAddTodo({
|
||||
id: Date.now(),
|
||||
title: title.trim(),
|
||||
completed: false,
|
||||
priority, // Agregamos la prioridad a la tarea
|
||||
},
|
||||
});
|
||||
|
||||
setTitle("");
|
||||
setPriority("media");
|
||||
priority,
|
||||
category,
|
||||
});
|
||||
setTitle('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="header">
|
||||
<h1>todos</h1>
|
||||
|
||||
<input
|
||||
className="new-todo"
|
||||
placeholder="Escribe una tarea"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
onKeyDown={handleSubmit}
|
||||
placeholder="What needs to be done?"
|
||||
onKeyDown={handleKeyDown}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<select value={priority} onChange={(e) => setPriority(e.target.value)}>
|
||||
<select value={priority} onChange={(e) => setPriority(e.target.value as any)}>
|
||||
<option value="alta">Alta</option>
|
||||
<option value="media">Media</option>
|
||||
<option value="baja">Baja</option>
|
||||
</select>
|
||||
<select value={category} onChange={(e) => setCategory(e.target.value)}>
|
||||
<option value="">Sin categoría</option>
|
||||
{categories.map((cat) => (
|
||||
<option key={cat} value={cat}>{cat}</option>
|
||||
))}
|
||||
</select>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
|
74
src/Item.tsx
74
src/Item.tsx
|
@ -1,64 +1,22 @@
|
|||
import { useRecordContext, useUpdate, useDelete } from "ra-core";
|
||||
import clsx from "clsx";
|
||||
import type { ChangeEvent } from "react";
|
||||
import type { Todo } from "./types";
|
||||
|
||||
export const Item = () => {
|
||||
const todo = useRecordContext<Todo>();
|
||||
const [update] = useUpdate();
|
||||
const [deleteTodo] = useDelete();
|
||||
import React from 'react';
|
||||
import { Todo } from './types';
|
||||
|
||||
if (!todo) return null;
|
||||
|
||||
const handleChangeCompleted = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
update("todos", {
|
||||
id: todo.id,
|
||||
data: { completed: event.currentTarget.checked },
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteTodo("todos", { id: todo.id });
|
||||
};
|
||||
|
||||
const getPriorityColor = (priority?: string) => {
|
||||
switch (priority) {
|
||||
case "alta":
|
||||
return "red";
|
||||
case "media":
|
||||
return "orange";
|
||||
case "baja":
|
||||
return "green";
|
||||
default:
|
||||
return "gray";
|
||||
}
|
||||
};
|
||||
interface ItemProps {
|
||||
todo: Todo;
|
||||
onToggle: (id: number) => void;
|
||||
onDelete: (id: number) => void;
|
||||
}
|
||||
|
||||
export const Item: React.FC<ItemProps> = ({ todo, onToggle, onDelete }) => {
|
||||
return (
|
||||
<li className={clsx({ completed: todo.completed })}>
|
||||
<div className="view">
|
||||
<input
|
||||
className="toggle"
|
||||
type="checkbox"
|
||||
onChange={handleChangeCompleted}
|
||||
checked={todo.completed}
|
||||
/>
|
||||
<label>
|
||||
{todo.title}
|
||||
{todo.priority && (
|
||||
<span
|
||||
style={{
|
||||
color: getPriorityColor(todo.priority),
|
||||
fontWeight: "bold",
|
||||
marginLeft: "0.5em",
|
||||
}}
|
||||
>
|
||||
({todo.priority})
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
<button onClick={handleDelete} className="destroy" />
|
||||
</div>
|
||||
</li>
|
||||
<div className="todo-item">
|
||||
<input type="checkbox" checked={todo.completed} onChange={() => onToggle(todo.id)} />
|
||||
<span className={todo.completed ? 'completed' : ''}>
|
||||
{todo.title} ({todo.priority})
|
||||
{todo.category && ` [${todo.category}]`}
|
||||
</span>
|
||||
<button onClick={() => onDelete(todo.id)}>❌</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import { RecordContextProvider, useListContext } from "ra-core";
|
||||
|
||||
import { Item } from "./Item";
|
||||
import type { Todo } from "./types";
|
||||
import React from 'react';
|
||||
import { Todo } from './types';
|
||||
import { Item } from './Item';
|
||||
|
||||
export const ItemList = () => {
|
||||
const { data, isPending, error } = useListContext<Todo>();
|
||||
if (isPending || error) return null;
|
||||
interface ItemListProps {
|
||||
todos: Todo[];
|
||||
onToggle: (id: number) => void;
|
||||
onDelete: (id: number) => void;
|
||||
}
|
||||
|
||||
export const ItemList: React.FC<ItemListProps> = ({ todos, onToggle, onDelete }) => {
|
||||
return (
|
||||
<ul className="todo-list">
|
||||
{data.map((todo) => (
|
||||
<RecordContextProvider value={todo} key={todo.id}>
|
||||
<Item />
|
||||
</RecordContextProvider>
|
||||
<div>
|
||||
{todos.map(todo => (
|
||||
<Item key={todo.id} todo={todo} onToggle={onToggle} onDelete={onDelete} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
interface SidebarProps {
|
||||
categories: string[];
|
||||
selectedCategory: string | null;
|
||||
onSelectCategory: (cat: string | null) => void;
|
||||
onAddCategory: (cat: string) => void;
|
||||
}
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ categories, selectedCategory, onSelectCategory, onAddCategory }) => {
|
||||
const [newCategory, setNewCategory] = useState('');
|
||||
|
||||
const handleAdd = () => {
|
||||
if (newCategory.trim() && !categories.includes(newCategory)) {
|
||||
onAddCategory(newCategory.trim());
|
||||
setNewCategory('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sidebar">
|
||||
<h3>Categorías</h3>
|
||||
<button onClick={() => onSelectCategory(null)} className={selectedCategory === null ? 'selected' : ''}>
|
||||
Todas
|
||||
</button>
|
||||
{categories.map((cat) => (
|
||||
<button key={cat} onClick={() => onSelectCategory(cat)} className={selectedCategory === cat ? 'selected' : ''}>
|
||||
{cat}
|
||||
</button>
|
||||
))}
|
||||
<div className="add-category">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nueva categoría"
|
||||
value={newCategory}
|
||||
onChange={(e) => setNewCategory(e.target.value)}
|
||||
/>
|
||||
<button onClick={handleAdd}>Agregar</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
|
@ -1,15 +1,19 @@
|
|||
import { ListBase } from "ra-core";
|
||||
|
||||
import { ItemList } from "./ItemList";
|
||||
import { Header } from "./Header";
|
||||
import { Footer } from "./Footer";
|
||||
import React from 'react';
|
||||
import { ItemList } from './ItemList';
|
||||
import { Footer } from './Footer';
|
||||
import { Todo } from './types';
|
||||
|
||||
export const TodoList = () => (
|
||||
<ListBase>
|
||||
<Header />
|
||||
<div className="main">
|
||||
<ItemList />
|
||||
<Footer />
|
||||
</div>
|
||||
</ListBase>
|
||||
interface TodoListProps {
|
||||
todos: Todo[];
|
||||
onToggle: (id: number) => void;
|
||||
onDelete: (id: number) => void;
|
||||
}
|
||||
|
||||
const TodoList: React.FC<TodoListProps> = ({ todos, onToggle, onDelete }) => (
|
||||
<div className="todo-list">
|
||||
<ItemList todos={todos} onToggle={onToggle} onDelete={onDelete} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default TodoList;
|
||||
|
|
80
src/app.css
80
src/app.css
|
@ -1,28 +1,64 @@
|
|||
/* used for things that should be hidden in the ui,
|
||||
but useful for people who use screen readers */
|
||||
.visually-hidden {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toggle-all {
|
||||
width: 40px !important;
|
||||
height: 60px !important;
|
||||
right: auto !important;
|
||||
.app {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.toggle-all-label {
|
||||
pointer-events: none;
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
background-color: #f3f3f3;
|
||||
padding: 1rem;
|
||||
height: 100vh;
|
||||
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 12px;
|
||||
}
|
||||
.sidebar h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.sidebar button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0.25rem 0;
|
||||
background: none;
|
||||
border: none;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar button.selected {
|
||||
font-weight: bold;
|
||||
color: #b83f45;
|
||||
}
|
||||
|
||||
.sidebar .add-category {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.sidebar .add-category input {
|
||||
width: 100%;
|
||||
padding: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex-grow: 1;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.todo-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.completed {
|
||||
text-decoration: line-through;
|
||||
color: gray;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
import './app.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
13
src/types.ts
13
src/types.ts
|
@ -1,7 +1,8 @@
|
|||
|
||||
export type Todo = {
|
||||
id: number;
|
||||
title: string;
|
||||
completed: boolean;
|
||||
priority?: "alta" | "media" | "baja";
|
||||
};
|
||||
|
||||
id: number;
|
||||
title: string;
|
||||
completed: boolean;
|
||||
priority: 'alta' | 'media' | 'baja';
|
||||
category?: string;
|
||||
};
|
||||
|
|
|
@ -1 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite/client" />
|
Loading…
Reference in New Issue