🔖

【NextJs14】NextJs14 と 便利なライブラリ【#29GetTodo & date-fns】

2024/02/09に公開

【#29GetTodo & date-fns】

YouTube: https://youtu.be/d25GONgHm_4

https://youtu.be/d25GONgHm_4

今回は前回作成したtodoのデータを取得して
アプリに表示します。

日付の部分で以下のライブラリを使用します。

https://date-fns.org/

npm i date-fns

まず、Shadcnのボタンに新しく「Completed」のvariantを追加します。

components/ui/button.tsx
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const buttonVariants = cva(
  "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive:
          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline:
          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
        primary: "bg-indigo-600 hover:bg-indigo-500 text-white",
        Completed: "bg-emerald-600 hover:bg-emerald-500 text-white",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : "button";
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    );
  }
);
Button.displayName = "Button";

export { Button, buttonVariants };

次に、表示するページは「Account」が現在空いている状態ですので
こちらに作成します。

※liに後の動画でkeyを設定します。
もし現状でコード上の赤線が出て気になる場合は
keyにtodoのid設定してみてください。

app/(main)/account/page.tsx
import { CheckCircle, Trash2 } from "lucide-react";
import { formatDistanceToNow } from "date-fns";

import { db } from "@/lib/db";

import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";

const getTodos = async () => {
  const todos = await db.todo.findMany({
    orderBy: {
      createdAt: "asc",
    },
  });
  return todos;
};

const AccountPage = async () => {
  const todos = await getTodos();

  return (
    <div className="p-6 w-full h-full">
      <div className="w-full h-full flex flex-col gap-y-3">
        <div className="flex flex-col md:flex-row items-center justify-between gap-2">
          <h2 className="text-center md:text-left text-3xl font-bold">Todos</h2>
          <div>form</div>
        </div>
        <Separator />
        <ul className="w-full space-y-3">
          {todos.map((todo, idx) => (
            <li className="flex flex-col md:flex-row items-center justify-between gap-x-4 gap-y-2">
              <div className="flex flex-col md:flex-row flex-1 items-center justify-between gap-x-3">
                <p className="text-xl font-semibold">
                  <span className="pr-2">{idx + 1}</span>
                  <span className={todo.isCompleted ? "line-through" : ""}>
                    {todo.title}
                  </span>
                </p>
                <p>
                  {formatDistanceToNow(todo.createdAt, { addSuffix: true })}
                </p>
              </div>
              <div className="flex items-center gap-x-2">
                <Button variant={todo.isCompleted ? "Completed" : "secondary"}>
                  <CheckCircle className="h-5 w-5" />
                </Button>
                <Button variant="destructive">
                  <Trash2 className="h-5 w-5" />
                </Button>
              </div>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default AccountPage;

Discussion