This commit is contained in:
2024-05-02 23:50:42 +02:00
parent 0cab278f45
commit 53984d9c89
29 changed files with 2818 additions and 1864 deletions

View File

@@ -1,17 +1,26 @@
import type { ButtonHTMLAttributes, DetailedHTMLProps, FC } from "react";
import type { ComponentPropsWithoutRef, FC } from "react";
type Props = DetailedHTMLProps<
ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>;
import { cn } from "@/helpers";
export const Button: FC<Props> = ({ className, ...rest }) => {
type Props = ComponentPropsWithoutRef<"button"> & {
variant?: "primary" | "outlined" | "icon";
};
export const Button: FC<Props> = ({
className,
variant = "primary",
...rest
}) => {
return (
<div>
<button
className={`rounded-md border border-gray-300 bg-sky-700 px-4 py-2 text-white focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-600 ${className}`}
{...rest}
/>
</div>
<button
className={cn(
"flex items-center justify-center rounded-md border border-gray-300 bg-sky-700 px-4 py-2 text-white focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-600",
variant === "outlined" && "border-sky-700 bg-inherit text-sky-700",
variant === "icon" &&
"h-6 w-6 shrink-0 border-none bg-inherit p-0 text-sky-700 hover:bg-slate-100 hover:text-sky-800",
className
)}
{...rest}
/>
);
};

View File

@@ -7,11 +7,9 @@ type Props = DetailedHTMLProps<
export const Input: FC<Props> = ({ className, ...rest }) => {
return (
<div>
<input
className={`w-full rounded-md border border-gray-300 px-4 py-2 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-600 ${className}`}
{...rest}
/>
</div>
<input
className={`w-full rounded-md border border-gray-300 px-4 py-2 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-600 ${className}`}
{...rest}
/>
);
};

View File

@@ -1,11 +1,21 @@
import type { ChangeEvent, FC, MouseEventHandler } from "react";
import type { ChangeEvent, FC, FormEvent, MouseEventHandler } from "react";
import { memo, useState } from "react";
import type { Task, Todolist as TodolistType } from "../../services/todolists";
import { Trash, Plus } from "lucide-react";
import type {
Task,
Todolist as TodolistType,
} from "../../services/todolist-api/todolists";
import { Button } from "../button";
import { Input } from "../input";
import {
ToggleGroup,
ToggleGroupItem,
} from "@/components/toggle-group/toggle-group";
import {
TaskStatus,
useCreateTaskMutation,
useDeleteTaskMutation,
useDeleteTodolistMutation,
@@ -29,7 +39,11 @@ export const Todolist: FC<Props> = memo(({ todolist }) => {
const [filter, setFilter] = useState("all");
const handleChangeStatus = (todolistId: string, task: Task) => {
const newTask = { ...task, status: task.status === 0 ? 2 : 0 };
const newTask: Task = {
...task,
status:
task.status === TaskStatus.DONE ? TaskStatus.OPEN : TaskStatus.DONE,
};
putTask({ todolistId, task: newTask });
};
@@ -39,7 +53,8 @@ export const Todolist: FC<Props> = memo(({ todolist }) => {
const handleDeleteTodolist = (todolistId: string) => {
deleteTodolist({ todolistId });
};
const handleAddTask = () => {
const handleAddTask = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
createTask({ todolistId: todolist.id, title: newTaskTitle });
setNewTaskTitle("");
};
@@ -47,17 +62,17 @@ export const Todolist: FC<Props> = memo(({ todolist }) => {
setNewTaskTitle(e.target.value);
};
const handleFilterChange: MouseEventHandler<HTMLButtonElement> = (e) => {
setFilter(e.currentTarget.value as Filter);
const handleFilterChange = (value: string) => {
setFilter(value as Filter);
};
if (isLoading) return <div>loading...</div>;
const filteredTasks = tasks?.items?.filter((task) => {
const filteredTasks = tasks?.filter((task) => {
switch (filter) {
case "active":
return task.status === 0;
return task.status === TaskStatus.OPEN;
case "completed":
return task.status === 2;
return task.status === TaskStatus.DONE;
default:
return true;
}
@@ -67,40 +82,65 @@ export const Todolist: FC<Props> = memo(({ todolist }) => {
<div
key={todolist.id}
className={
"w- flex w-72 flex-col gap-3 rounded-md border border-gray-300 p-4"
"flex w-72 flex-col gap-3 rounded-md border border-gray-300 p-4"
}
>
<div className={"flex items-center justify-between "}>
<h2 className={"text-xl"}>{todolist.title}</h2>
<button onClick={() => handleDeleteTodolist(todolist.id)}>x</button>
<Button
variant={"icon"}
onClick={() => handleDeleteTodolist(todolist.id)}
>
<Trash size={16} />
</Button>
</div>
<div className={"flex w-52 gap-4"}>
<form
onSubmit={handleAddTask}
className={"relative flex w-full items-center gap-4"}
>
<Input value={newTaskTitle} onChange={handleNewTaskTitleChange} />
<Button onClick={handleAddTask}>+</Button>
</div>
{filteredTasks?.map((task) => (
<div className={"flex items-center gap-2"} key={task.id}>
<input
onChange={() => handleChangeStatus(todolist.id, task)}
type={"checkbox"}
checked={[0, 1, 3].includes(task.status)}
/>
<div key={task.id}>{task.title}</div>
<button onClick={() => handleDeleteTask(todolist.id, task.id)}>
X
</button>
</div>
))}
<Button type={"submit"} variant={"icon"} className={"absolute right-4"}>
<Plus size={16} />
</Button>
</form>
{filteredTasks?.map((task) => {
return (
<div className={"flex cursor-pointer items-center"} key={task.id}>
<label className={"flex cursor-pointer items-center gap-2"}>
<input
onChange={() => handleChangeStatus(todolist.id, task)}
type={"checkbox"}
checked={TaskStatus.DONE === task.status}
/>
{task.title}
</label>
<Button
variant={"icon"}
onClick={() => handleDeleteTask(todolist.id, task.id)}
className={"ml-2"}
>
<Trash size={16} />
</Button>
</div>
);
})}
<div className={"flex"}>
<Button onClick={handleFilterChange} value={"all"}>
All
</Button>
<Button onClick={handleFilterChange} value={"active"}>
Active
</Button>
<Button onClick={handleFilterChange} value={"completed"}>
Completed
</Button>
<ToggleGroup
type="single"
onValueChange={handleFilterChange}
value={filter}
className={"w-full"}
>
<ToggleGroupItem className={"w-full"} value="all">
All
</ToggleGroupItem>
<ToggleGroupItem className={"w-full"} value="active">
Active
</ToggleGroupItem>
<ToggleGroupItem className={"w-full"} value="completed">
Completed
</ToggleGroupItem>
</ToggleGroup>
</div>
</div>
);

View File

@@ -0,0 +1,83 @@
"use client";
import * as React from "react";
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
import type { VariantProps } from "class-variance-authority";
import { cva } from "class-variance-authority";
import { cn } from "@/helpers";
const toggleVariants = cva(
"inline-flex border border-sky-700 items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-sky-700 data-[state=on]:text-white",
{
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-9 px-3",
sm: "h-8 px-2",
lg: "h-10 px-3",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
});
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, children, ...props }, ref) => (
<ToggleGroupPrimitive.Root
ref={ref}
className={cn("flex items-center justify-center gap-1", className)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
));
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext);
return (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
className
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
);
});
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
export { ToggleGroup, ToggleGroupItem };