😊

Reactでローカル用ガントチャートアプリ作成までの備忘録②

に公開

Next.jsでガントチャートアプリを作っていきます。

npx create-next-app@latest next-gantt
 Would you like to use TypeScript? No / Yes
 Would you like to use ESLint? No / Yes
 Would you like to use Tailwind CSS? No / Yes
 Would you like your code inside a `src/` directory? No / Yes
 Would you like to use App Router? (recommended) … No / Yes
 Would you like to use Turbopack for `next dev`? … No / Yes
 Would you like to customize the import alias (`@/*` by default)? … No / Yes

shadcnを入れていきます。
ちなみに今回はreact19を使用します。

npx shadcn@latest init

上のコマンドを実行して以下のように選択しました。
要約するとテーマはSlateで--legacy-peer-depsオプションでインストールしています。

 Preflight checks.
 Verifying framework. Found Next.js.
 Validating Tailwind CSS.
 Validating import alias.
 Which style would you like to use? Default
 Which color would you like to use as the base color? Slate
 Would you like to use CSS variables for theming? no / yes
 Writing components.json.
 Checking registry.
 Updating tailwind.config.ts
 Updating app/globals.css
  Installing dependencies.

It looks like you are using React 19. 
Some packages may fail to install due to peer dependency issues in npm (see https://ui.shadcn.com/react-19).

 How would you like to proceed? Use --legacy-peer-deps
 Installing dependencies.
 Created 1 file:
  - lib/utils.ts

Success! Project initialization completed.
You may now add components.

ここの対応はshadcnの公式にも記述しています。

実行した後にcomponents.jsonが生成されています。
16行目あたりに以下のように記述されているので
shadcnからコンポーネントをインストールされると以下のところに入ります。

components.json
 "ui": "@/components/ui",

とりあえずボタンを表示してみます。

npx shadcn@latest add

色々なコンポーネントが表示されるのでスペースを押して
選択した後にエンターでダウンロードします。

ここでもforceかlegacyか聞かれますがlegacyを選択します。

 How would you like to proceed? Use --legacy-peer-deps

今回は試しにボタンを入れてapp直下のpage.tsxを以下のように記述してみます。

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

export default function Home() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <Button className="font-bold">shadcn テストボタン</Button>
    </div>
  );
}

以下のようになっていればOKです。

これで準備OKです。

前回の記事で紹介した「gantt-task-react」をインストールします。
私はreact19を使用しているのでlegacyオプションをつけないとエラーが出るのでオプションをつけました。

npm i gantt-task-react --legacy-peer-deps

とりあえず公式のコードを見て、少し変更して動作確認します。
それが以下になります。

最近話題のdeepseek先生に手伝ってもらいました。

app/page.tsx
"use client";
import { Button } from "@/components/ui/button";
import { Gantt, Task, ViewMode } from "gantt-task-react";
import "gantt-task-react/dist/index.css"; // CSSファイルをインポート。これを忘れていると真っ黒のガントチャートになる…

export default function Home() {
  const tasks: Task[] = [
    {
      start: new Date(2020, 1, 1),
      end: new Date(2020, 1, 2),
      name: "Idea",
      id: "Task 0",
      type: "task",
      progress: 45,
      isDisabled: true,
      styles: { progressColor: "#ffbb54", progressSelectedColor: "#ff9e0d" },
    },
  ];

  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <div className="flex flex-col items-center w-full p-4 space-y-4">
        <Button className="font-bold">shadcn テストボタン</Button>

        {/* Gantt コンポーネントのサイズを制限 */}
        <div className="w-full max-w-4xl overflow-x-auto">
          <Gantt
            tasks={tasks}
            viewMode={ViewMode.Day} // 表示モードを調整
            columnWidth={60} // カラムの幅を調整
            rowHeight={40} // 行の高さを調整
            fontSize="12" // フォントサイズを調整
          />
        </div>
      </div>
    </div>
  );
}

npm run dev

以上で動作確認をして下のように表示されていればOKです。

ここで少しファイル分割を行います。
componentsディレクトリ直下に「GanttView.tsx」を作成して
以下のように記述します。

GanttView.tsx
import { Gantt, Task, ViewMode } from "gantt-task-react";
import React from "react";
import "gantt-task-react/dist/index.css"; // CSSファイルをインポート

const GanttView = () => {
  const tasks: Task[] = [
    {
      start: new Date(2020, 1, 1),
      end: new Date(2020, 1, 2),
      name: "Idea",
      id: "Task 0",
      type: "task",
      progress: 45,
      isDisabled: true,
      styles: { progressColor: "#ffbb54", progressSelectedColor: "#ff9e0d" },
    },
  ];
  return (
    <div className="w-full max-w-4xl overflow-x-auto">
      <Gantt
        tasks={tasks}
        viewMode={ViewMode.Day} // 表示モードを調整
        columnWidth={60} // カラムの幅を調整
        rowHeight={40} // 行の高さを調整
        fontSize="12" // フォントサイズを調整
      />
    </div>
  );
};

export default GanttView;

なのでpage.tsxは以下のようになります。

page.tsx
"use client";
import GanttView from "@/components/GanttView";
import { Button } from "@/components/ui/button";

export default function Home() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <div className="flex flex-col items-center w-full p-4 space-y-4">
        <Button className="font-bold">shadcn テストボタン</Button>
        <GanttView />
      </div>
    </div>
  );
}

この分割をしてエラーが出なければOKです。

次はボタンを押した時にタスクを追加するダイアログが欲しいので実装していきます。
もちろんshadcnのダイアログを使用していきます。(modalが好きな人はそちらで)

npx shadcn@latest add dialog

最初はダイアログを表示するだけを実装したいのでshadcnの公式サイトを見てサンプルコードを使います。

今回も少し編集しています。
shadcnのdialogを編集したい方は以下の記事が参考になりそうです。
https://zenn.dev/ikoamu/articles/636fabfcfd3bcc
componentsディレクトリ直下にTaskAddDialog.tsxを作成します。

components/TaskAddDialog.tsx
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "./ui/button";

// TaskAddDialogPropsの型定義
// page.tsxからはisOpenとonRequestCloseを受け取る
interface TaskAddDialogProps {
  isOpen: boolean;
  onRequestClose?: () => void;
}

const TaskAddDialog = ({ isOpen, onRequestClose }: TaskAddDialogProps) => {
  return (
    <Dialog open={isOpen} onOpenChange={onRequestClose}>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>ここにダイアログのタイトルを入力</DialogTitle>
          <DialogDescription>ここに説明文などを入力</DialogDescription>
        </DialogHeader>
        {/* 閉じるボタン追加 */}
        <DialogClose asChild>
          <Button type="button" variant="secondary">
            Close
          </Button>
        </DialogClose>
      </DialogContent>
    </Dialog>
  );
};

export default TaskAddDialog;

何度も申し訳ないですがpage.tsxを以下のようにします。

page.tsx
"use client";
import GanttView from "@/components/GanttView";
import TaskAddDialog from "@/components/TaskAddDialog";
import { Button } from "@/components/ui/button";
import { useState } from "react";

export default function Home() {
  // ダイアログを開くための状態を管理
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <div className="flex flex-col items-center w-full p-4 space-y-4">
        <Button
          className="font-bold"
          onClick={() => {
            setIsOpen(true);
          }}
        >
          shadcn テストボタン
        </Button>
        <GanttView />
        <TaskAddDialog
          isOpen={isOpen}
          onRequestClose={() => {
            setIsOpen(false);
          }}
        />
      </div>
    </div>
  );
}

これでダイアログが表示されたらOKです。
あとはダイアログの入力画面の実装や、削除、更新などの実装ぐらいかと思います。

役に立つか分かりませんが次はその辺の実装を行います。

Discussion