Version 3.0

This commit is contained in:
angel.alducin 2025-03-27 00:05:37 -06:00
parent deb60c112f
commit 805ff8d249
30 changed files with 355 additions and 321 deletions

View File

@ -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>

18
node_modules/.package-lock.json generated vendored
View File

@ -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"
},

View File

@ -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
}
},

2
node_modules/@types/node/README.md generated vendored
View File

@ -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

11
node_modules/@types/node/fs.d.ts generated vendored
View File

@ -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,

View File

@ -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"
}

View File

@ -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

View File

@ -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;

View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -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": [

View File

@ -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"

View File

@ -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"}

18
package-lock.json generated
View File

@ -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"
},

13
public/index.html Normal file
View File

@ -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>

BIN
src.zip Normal file

Binary file not shown.

View File

@ -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;

View File

@ -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>;
};

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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>
);
};

45
src/Sidebar.tsx Normal file
View File

@ -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;

View File

@ -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;

View File

@ -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;
}

10
src/main.tsx Normal file
View File

@ -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>,
)

View File

@ -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;
};

2
src/vite-env.d.ts vendored
View File

@ -1 +1 @@
/// <reference types="vite/client" />
/// <reference types="vite/client" />