1
0
Fork 0

Task list route

This commit is contained in:
Tomas Balsys 2024-10-26 18:52:50 +03:00
parent d80aa2eb5f
commit 45c7fdc28a
10 changed files with 200 additions and 17 deletions

View File

@ -9,7 +9,7 @@
</head>
<body class="m-0 min-h-screen">
<div id="root" class="my-0 mx-auto"></div>
<div id="root" class="my-0 mx-auto max-w-screen-sm"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

View File

@ -0,0 +1,15 @@
import { } from 'react'
export function AccordionItem({ title, children, isOpen, onClick }: { title: string; children: JSX.Element; isOpen: boolean, onClick: () => void; }) {
return (
<>
<button type="button" className="accordion-title" onClick={onClick}>
<span>{title}</span>
<svg className={"w-3 h-3 shrink-0" + (isOpen ? "" : " rotate-180")} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5 5 1 1 5" />
</svg>
</button>
{isOpen && <div className="accordion-body">{children}</div>}
</>
);
}

11
src/components/consts.ts Normal file
View File

@ -0,0 +1,11 @@
export const taskTypes = {
task: 'Užduotis',
bug: "Bug'as",
};
export const taskStatuses = {
paused: 'Laukia',
'in progress': 'Daroma',
testing: 'Testuojama',
released: 'Padaryta',
};

View File

@ -0,0 +1,10 @@
import { } from 'react'
export function EnumSelect({ name, entries }: { name: string; entries: Record<string, string> }): JSX.Element {
return (
<select id={name} name={name}>
{Object.entries(entries).map(([value, name]) => <option key={value} value={value}>{name}</option>)}
</select>
);
}

View File

@ -0,0 +1,63 @@
import { } from 'react'
import { taskTypes, taskStatuses } from './consts';
import { EnumSelect } from './enum-select';
export function TaskForm({ tasks, setTasks, setExpanded }: { tasks: TodoTasks, setTasks: (t: TodoTasks) => void, setExpanded: (p: number) => void }): JSX.Element {
return (
<form className="grid grid-cols-1 gap-y-8" onSubmit={event => {
event.preventDefault();
const data = Object.fromEntries(new FormData(event.currentTarget));
const newTask = {
id: tasks.reduce((max, row) => row.id > max ? row.id : max, 1) + 1,
type: data.type as TaskType,
title: data.title as string,
status: data.status as TaskStatus,
};
if (data.insert) {
setTasks([newTask, ...tasks]);
} else {
setTasks([...tasks, newTask]);
}
setExpanded(0);
}}>
<div>
<label htmlFor="type">Tipas</label>
<div className="mt-2">
<EnumSelect name="type" entries={taskTypes} />
</div>
</div>
<div>
<label htmlFor="title">Pavadinimas</label>
<div className="mt-2">
<input type="text" id="title" name="title" className="" />
</div>
</div>
<div>
<label htmlFor="type">Statusas</label>
<div className="mt-2">
<EnumSelect name="status" entries={taskStatuses} />
</div>
</div>
<div className="flex items-center mb-4">
<input id="insert" name="insert" type="checkbox" className="w-5 h-5 accent-indigo-500" />
<label htmlFor="insert" className="ms-2">Įterpti pradžioje</label>
</div>
<div className="mt-2 flex items-center justify-end gap-x-6">
<button type="button" className="font-semibold" onClick={() => setExpanded(0)}>
Cancel
</button>
<button
type="submit"
className="bg-indigo-500 rounded-lg px-3 py-2 font-semibold text-white hover:bg-indigo-600"
>
Save
</button>
</div>
</form>
);
}

View File

@ -0,0 +1,24 @@
import { } from 'react'
import { taskTypes, taskStatuses } from './consts';
export function TaskList({ tasks }: { tasks: TodoTasks }): JSX.Element {
return (
<table className="w-full">
<thead>
<tr>
<th>Tipas</th>
<th>Pavadinimas</th>
<th>Statusas</th>
</tr>
</thead>
<tbody>
{tasks.map(task => <tr key={task.id}>
<td>{taskTypes[task.type]}</td>
<td>{task.title}</td>
<td>{taskStatuses[task.status]}</td>
</tr>)}
</tbody>
</table>
);
}

View File

@ -2,18 +2,42 @@
@tailwind components;
@tailwind utilities;
@layer base {
h1 {
@apply text-2xl my-5;
}
@layer utilities {
p,
li {
@apply my-4;
}
a.nav-button {
@apply bg-neutral-100 dark:bg-neutral-900 cursor-pointer px-5 py-2 rounded-lg border border-transparent hover:border-indigo-500 transition-colors duration-1000;
@apply bg-neutral-100 dark:bg-neutral-900 cursor-pointer px-5 py-2 rounded-lg border border-gray-300 hover:border-indigo-500 transition-colors duration-700;
}
th {
@apply border-b dark:border-slate-600 pl-8 pt-0 pb-3 text-slate-400 dark:text-slate-200 text-left;
}
td {
@apply border-b border-slate-100 dark:border-slate-700 p-4 pl-8 text-slate-500 dark:text-slate-400;
}
select, input {
@apply w-full rounded-lg border-0 px-3 py-2 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-500 focus:outline-none hover:ring-indigo-500 transition-shadow duration-700;
}
.accordion-title {
@apply text-2xl flex items-center justify-between w-full p-5 font-medium border border-b-0 border-slate-200 dark:focus:ring-slate-800 dark:border-slate-700 hover:bg-slate-100 dark:hover:bg-slate-800 gap-3;
}
.accordion-title:first-child {
@apply rounded-t-xl;
}
.accordion-title:last-child, .accordion-body:last-child {
@apply border-b rounded-b-xl;
}
.accordion-body {
@apply p-5 border border-b-0 border-slate-200 dark:border-slate-700;
}
}

View File

@ -1,16 +1,42 @@
import { useState } from 'react'
import { AccordionItem } from '../components/accordion-item';
import { TaskList } from '../components/task-list';
import { TaskForm } from '../components/task-form';
function App() {
const [count, setCount] = useState(0)
const [tasks, setTasks] = useState<TodoTasks>([
{
id: 1,
type: 'task',
title: 'Lipk iš lovos',
status: 'in progress',
},
{
id: 2,
type: 'task',
title: 'Susirask kavos',
status: 'paused',
},
{
id: 3,
type: 'bug',
title: 'Sudaryk TODO sąrašą',
status: 'testing',
},
]);
const [expanded, setExpanded] = useState(0);
const handleAccordion = (panel: number) => setExpanded(expanded == panel ? -1 : panel);
return (
<>
<h1>Vite + React</h1>
<div>
<button onClick={() => setCount((count) => count + 1)} className="bg-neutral-100 dark:bg-neutral-900 cursor-pointer px-5 py-2 rounded-lg border border-transparent hover:border-indigo-500 transition-colors duration-1000">
count is {count}
</button>
</div>
<AccordionItem title='Užduočių sąrašas' isOpen={expanded == 0} onClick={() => handleAccordion(0)}>
<TaskList tasks={tasks} />
</AccordionItem>
<AccordionItem title='Nauja užduotis' isOpen={expanded == 1} onClick={() => handleAccordion(1)}>
<TaskForm tasks={tasks} setTasks={setTasks} setExpanded={setExpanded} />
</AccordionItem>
</>
)
}

View File

@ -7,10 +7,8 @@ export default function Root() {
<Link to={`/list`} className="nav-button">Sąrašas</Link>
<Link to={`/fetch`} className="nav-button">Duomenys</Link>
</nav>
<main className="flex justify-center">
<article>
<main className="my-10">
<Outlet />
</article>
</main>
</>
);

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

@ -1 +1,13 @@
/// <reference types="vite/client" />
type TaskType = 'task' | 'bug';
type TaskStatus = 'paused' | 'in progress' | 'testing' | 'released';
class TodoTask {
id: number;
type: TaskType;
title: string;
status: TaskStatus;
}
type TodoTasks = TodoTask[];