Feature: add todo widget
This commit is contained in:
parent
1c47d9d70e
commit
b5a8e8aaac
13
docs/widgets/services/todo.md
Normal file
13
docs/widgets/services/todo.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
title: Todo
|
||||
description: Todo Widget Configuration
|
||||
---
|
||||
|
||||
The Todo widget provides a simple to-do list functionality.
|
||||
It allows users to add, remove, and mark tasks as completed.
|
||||
The component utilizes local storage to persist the to-do list data even when the user refreshes the page.
|
||||
|
||||
```yaml
|
||||
widget:
|
||||
type: todo
|
||||
```
|
||||
@ -122,6 +122,7 @@ nav:
|
||||
- widgets/services/syncthing-relay-server.md
|
||||
- widgets/services/tailscale.md
|
||||
- widgets/services/tdarr.md
|
||||
- widgets/services/todo.md
|
||||
- widgets/services/traefik.md
|
||||
- widgets/services/transmission.md
|
||||
- widgets/services/truenas.md
|
||||
|
||||
@ -96,6 +96,7 @@ const components = {
|
||||
tautulli: dynamic(() => import("./tautulli/component")),
|
||||
tdarr: dynamic(() => import("./tdarr/component")),
|
||||
traefik: dynamic(() => import("./traefik/component")),
|
||||
todo: dynamic(() => import("./todo/component")),
|
||||
transmission: dynamic(() => import("./transmission/component")),
|
||||
tubearchivist: dynamic(() => import("./tubearchivist/component")),
|
||||
truenas: dynamic(() => import("./truenas/component")),
|
||||
|
||||
96
src/widgets/todo/component.jsx
Normal file
96
src/widgets/todo/component.jsx
Normal file
@ -0,0 +1,96 @@
|
||||
("use client");
|
||||
|
||||
import { MdOutlineDelete } from "react-icons/md";
|
||||
import { useEffect, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import Container from "components/services/widget/container";
|
||||
|
||||
export default function Component({ service }) {
|
||||
const [todos, setTodos] = useState([]);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
function handleChange(e) {
|
||||
setInputValue(e.target.value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const localStorageTodos = localStorage.getItem("todos");
|
||||
|
||||
const parsedTodos = localStorageTodos !== null ? JSON.parse(localStorageTodos) : [];
|
||||
|
||||
setTodos(parsedTodos);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (todos.length === 0) return;
|
||||
localStorage.setItem("todos", JSON.stringify(todos));
|
||||
}, [todos]);
|
||||
|
||||
function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (inputValue.trim() === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
setTodos([...todos, inputValue]);
|
||||
setInputValue("");
|
||||
}
|
||||
|
||||
const handleDelete = (index) => {
|
||||
const newTodos = [...todos];
|
||||
newTodos.splice(index, 1);
|
||||
setTodos(newTodos);
|
||||
|
||||
if (newTodos.length === 0) {
|
||||
localStorage.removeItem("todos");
|
||||
} else {
|
||||
localStorage.setItem("todos", JSON.stringify(newTodos));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container service={service}>
|
||||
<div>
|
||||
{todos.map((todo, index) => (
|
||||
<div key={`${todo}_${index}`} className="flex flex-row items-center">
|
||||
<input type="checkbox" className="bg-theme-200/50 dark:bg-theme-900/20 rounded m-1" />
|
||||
<input
|
||||
className={classNames(
|
||||
"bg-theme-200/50 dark:bg-theme-900/20 rounded m-1 flex-1 flex flex-col items-center justify-center text-center p-1",
|
||||
inputValue === undefined ? "animate-pulse" : "",
|
||||
"service-block",
|
||||
)}
|
||||
type="text"
|
||||
value={todo}
|
||||
onChange={handleChange}
|
||||
disabled
|
||||
/>
|
||||
<button type="button" onClick={() => handleDelete(index)}>
|
||||
<MdOutlineDelete />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<form className="flex flex-row items-center">
|
||||
<input
|
||||
type="submit"
|
||||
className="bg-theme-200/50 dark:bg-theme-900/20 rounded m-1 w-4 h-4 flex flex-col items-center justify-center text-center p-1 border border-[#6b7280]"
|
||||
onClick={handleSubmit}
|
||||
value="+"
|
||||
/>
|
||||
<input
|
||||
className={classNames(
|
||||
"bg-theme-200/50 dark:bg-theme-900/20 rounded m-1 flex-1 flex flex-col items-center justify-center text-center p-1",
|
||||
inputValue === undefined ? "animate-pulse" : "",
|
||||
"service-block",
|
||||
)}
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
10
src/widgets/todo/widget.js
Normal file
10
src/widgets/todo/widget.js
Normal file
@ -0,0 +1,10 @@
|
||||
const widget = {
|
||||
mappings: {
|
||||
"todo/latest": {
|
||||
endpoint: "todo/latest",
|
||||
validate: ["data"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default widget;
|
||||
@ -88,6 +88,7 @@ import strelaysrv from "./strelaysrv/widget";
|
||||
import tailscale from "./tailscale/widget";
|
||||
import tautulli from "./tautulli/widget";
|
||||
import tdarr from "./tdarr/widget";
|
||||
import todo from "./todo/widget";
|
||||
import traefik from "./traefik/widget";
|
||||
import transmission from "./transmission/widget";
|
||||
import tubearchivist from "./tubearchivist/widget";
|
||||
@ -193,6 +194,7 @@ const widgets = {
|
||||
tailscale,
|
||||
tautulli,
|
||||
tdarr,
|
||||
todo,
|
||||
traefik,
|
||||
transmission,
|
||||
tubearchivist,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user