4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScript版PleasanterClientをバージョンアップ-①Create

Posted at

前置き

以前から、こちらの記事のとおりPleasaterのAPIを使いやすくするために、TypeScriptで開発を進めています。今回はその続きです。

以前までは、Getしか処理がなかったので、今回はCreateを実装させてみたいと思います。

環境

主な環境は以下のとおり

  • next: 14.1.0
  • react: 18
  • tailwindcss: 3.3.0

前回構築したNext.jsの環境をアップデートしていきます。

作るもののイメージ

前回作った画面に加え、記事投稿用の画面を追加します。

image.png
非常に簡易的な入力するだけの画面です。

ソース

以下のような構成になっています。

app
├api //Pleansater接続のため、api routeで制御します。
│└pleasanter
│ ├getarticles
│ │└route.ts //記事取得用api
│ └postarticle
│  └route.ts //記事投稿用api
├components
│└PleasanterItem.tsx //pleasnaterから取得した情報を描画するコンポーネント
├lib
│└pleasanterClient.ts //pleasnater接続用モジュール群(今回のメイン)
├post
│└page.tsx //投稿用ページのpage.tsx
└page.tsx //メイン画面のpage.tsx

詳しくはgitを参照してください。

前回、かなり雑な状態だったので、多少整理しました。が、webアプリとしては全然機能しません。あくまでpleasnater接続の動作を確認するためと割り切ってます。

Pleasanterの使用

CreateのAPI使用はマニュアルを参照してください。

解説

主要なポイントを掻い摘んで説明していきます。

PleasanterClient.ts

本記事の主たる要素です。前回から追加したcreate要素をピックアップ

ItemModel

export class ItemModel extends Api implements RequestOpsions {
    SiteId?: number | null;
    UpdatedTime?: Date | null;
    IssueId?: Number | null;
    ResutId?: Number | null;
    Ver?: Number | null;
    Title?: string | null;
    Body?: string | null;
    StartTime?: Date | null;
    CompletionTime?: Date | null;
    WorkValue?: Number | null;
    ProgressRate?: Number | null;
    Status?: Number | null;
    Manager?: Number | null;
    Owner?: Number | null;
    Locked?: boolean | null;
    Comments?: string | null;
    Creator?: Number | null;
    Updator?: Number | null;
    CreatedTime?: Date | null;
    ProcessId?: Number | null;
    ClassHash: Map<string, string> | null;
    DateHash: Map<string, Date> | null;
    NumHash: Map<string, Number> | null;
    CheckHash: Map<string, boolean> | null;
    DescriptionHash: Map<string, string> | null;
    AttachmentsHash: Map<string, any> | null;
    constructor(requestOpsions: RequestOpsions) {
        super({});
        this.SiteId = requestOpsions.SiteId || null;
        this.UpdatedTime = requestOpsions.UpdatedTime || null;
        this.IssueId = requestOpsions.IssueId || null;
        this.ResutId = requestOpsions.ResutId || null;
        this.Ver = requestOpsions.Ver || null;
        this.Title = requestOpsions.Title || null;
        this.Body = requestOpsions.Body || null;
        this.StartTime = requestOpsions.StartTime || null;
        this.CompletionTime = requestOpsions.CompletionTime || null;
        this.WorkValue = requestOpsions.WorkValue || null;
        this.ProgressRate = requestOpsions.ProgressRate || null;
        this.Status = requestOpsions.Status || null;
        this.Manager = requestOpsions.Manager || null;
        this.Owner = requestOpsions.Owner || null;
        this.Locked = requestOpsions.Locked || null;
        this.Comments = requestOpsions.Comments || null;
        this.Creator = requestOpsions.Creator || null;
        this.Updator = requestOpsions.Updator || null;
        this.CreatedTime = requestOpsions.CreatedTime || null;
        this.ProcessId = requestOpsions.ProcessId || null;
        this.ClassHash = requestOpsions.ClassHash || null;
        this.DateHash = requestOpsions.DateHash || null;
        this.NumHash = requestOpsions.NumHash || null;
        this.CheckHash = requestOpsions.CheckHash || null;
        this.DescriptionHash = requestOpsions.DescriptionHash || null;
        this.AttachmentsHash = requestOpsions.AttachmentsHash || null;
    }

    setClassHash({ key, value }: { key: string; value: string }) {
        if (this.ClassHash === null) {
            this.ClassHash = new Map();
        }
        this.ClassHash.set(key, value);
    }

    setDateHash({ key, value }: { key: string; value: Date }) {
        if (this.DateHash === null) {
            this.DateHash = new Map();
        }
        this.DateHash.set(key, value);
    }

    setNumHash({ key, value }: { key: string; value: Number }) {
        if (this.NumHash === null) {
            this.NumHash = new Map();
        }
        this.NumHash.set(key, value);
    }

    setDescriptionHash({ key, value }: { key: string; value: string }) {
        if (this.DescriptionHash === null) {
            this.DescriptionHash = new Map();
        }
        this.DescriptionHash.set(key, value);
    }

    setAttachmentsHash({
        key,
        value,
    }: {
        key: string;
        value: AttachmentsHashProps;
    }) {
        if (this.AttachmentsHash === null) {
            this.AttachmentsHash = new Map();
        }
        this.AttachmentsHash.set(key, value);
    }
}

createリクエストの肝となる、itemモデルです。基本的にpleasanter本体の仕様を踏襲しています。

setItemModel

static setItemModel({ item }: { item: ItemModel }) {
        let data: any = {};
        data.ApiKey = item.ApiKey;
        data.ApiVersion = item.ApiVersion;

        if (item.SiteId != null) {
            data.SiteId = item.SiteId;
        }

        if (item.UpdatedTime != null) {
            data.UpdatedTime = item.UpdatedTime;
        }

        if (item.IssueId != null) {
            data.IssueId = item.IssueId;
        }

        if (item.ResutId != null) {
            data.ResutId = item.ResutId;
        }

        if (item.Ver != null) {
            data.Ver = item.Ver;
        }

        if (item.Title != null) {
            data.Title = item.Title;
        }

        if (item.Body != null) {
            data.Body = item.Body;
        }

        if (item.StartTime != null) {
            data.StartTime = item.StartTime;
        }

        if (item.CompletionTime != null) {
            data.CompletionTime = item.CompletionTime;
        }

        if (item.WorkValue != null) {
            data.WorkValue = item.WorkValue;
        }

        if (item.ProgressRate != null) {
            data.ProgressRate = item.ProgressRate;
        }

        if (item.Status != null) {
            data.Status = item.Status;
        }

        if (item.Manager != null) {
            data.Manager = item.Manager;
        }

        if (item.Owner != null) {
            data.Owner = item.Owner;
        }

        if (item.Locked != null) {
            data.Locked = item.Locked;
        }

        if (item.Comments != null) {
            data.Comments = item.Comments;
        }

        if (item.Creator != null) {
            data.Creator = item.Creator;
        }

        if (item.Updator != null) {
            data.Updator = item.Updator;
        }

        if (item.CreatedTime != null) {
            data.CreatedTime = item.CreatedTime;
        }

        if (item.ProcessId != null) {
            data.ProcessId = item.ProcessId;
        }

        if (item.ClassHash) {
            let hash: any = {};
            item["ClassHash"].forEach((value, key) => {
                hash[key] = value;
            });
            data["ClassHash"] = hash;
        }

        if (item.DateHash) {
            let hash: any = {};
            item["DateHash"].forEach((value, key) => {
                hash[key] = value;
            });
            data["DateHash"] = hash;
        }

        if (item.NumHash) {
            let hash: any = {};
            item["NumHash"].forEach((value, key) => {
                hash[key] = value;
            });
            data["NumHash"] = hash;
        }

        if (item.CheckHash) {
            let hash: any = {};
            item["CheckHash"].forEach((value, key) => {
                hash[key] = value;
            });
            data["CheckHash"] = hash;
        }

        if (item.DescriptionHash) {
            let hash: any = {};
            item["DescriptionHash"].forEach((value, key) => {
                hash[key] = value;
            });
            data["DescriptionHash"] = hash;
        }

        if (item.AttachmentsHash) {
            let hash: any = {};
            item["AttachmentsHash"].forEach((value, key) => {
                hash[key] = value;
            });
            data["AttachmentsHash"] = hash;
        }
        return data;
    }

createリクエストを生成するメソッドです。「ClassHash」だとかの面倒な部分は、項目を渡せばいい感じに処理してくれるようにしています。

apiCreate

    static async apiCreate({
        id,
        item,
        url,
    }: {
        id: number;
        item: ItemModel;
        url: string;
    }) {
        let data = PleasanterApiClient.setItemModel({ item: item });
        let response = await fetch(
            `${url}${url.slice(-1) !== "/" ? "/" : ""}api/items/${id}/create`,
            {
                method: "POST",
                body: JSON.stringify(data),
                headers: {
                    "Content-Type": "application/json",
                },
            }
        );
        return response.json();
    }

create本体です。itemModelを引数で受け取り、fetchでpleasanterに接続、responseを返却しています。

post/page.tsx

"use client";

import React, { useState } from "react";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { useRouter } from "next/navigation";

const page = () => {
    const router = useRouter();
    const [title, setTitle] = useState("");
    const [body, setBody] = useState("");
    const [classA, setClassA] = useState("");
    const [classB, setClassB] = useState("");

    const handleSubmit = async (e: any) => {
        e.preventDefault();

        const response = await fetch(
            "http://localhost:3000/api/pleasanter/postarticle",
            {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({
                    title: title,
                    body: body,
                    classA: classA,
                    classB: classB,
                }),
            }
        );
        router.push("/");
    };
    return (
        <div className="my-10">
            <div className="ml-[250px]">
                <form onSubmit={handleSubmit}>
                    <div className=" flex flex-col gap-4 w-[200px] items-center">
                        <div className="flex items-center gap-3">
                            <p>Title</p>
                            <input
                                className="border h-10"
                                type="text"
                                name="title"
                                value={title}
                                onChange={(e) => setTitle(e.target.value)}
                            />
                        </div>
                        <div className="flex items-center gap-3">
                            <p>Body</p>
                            <input
                                className="border h-10"
                                type="text"
                                name="Body"
                                value={body}
                                onChange={(e) => setBody(e.target.value)}
                            />
                        </div>
                        <div className="flex items-center gap-3">
                            <p>ClassA</p>
                            <input
                                className="border h-10"
                                type="text"
                                name="classA"
                                value={classA}
                                onChange={(e) => setClassA(e.target.value)}
                            />
                        </div>
                        <div className="flex items-center gap-3">
                            <p>ClassB</p>
                            <input
                                className="border h-10"
                                type="text"
                                name="classB"
                                onChange={(e) => setClassB(e.target.value)}
                            />
                        </div>
                        <Button
                            className=" w-[150px]"
                            variant="outline"
                            type="submit"
                        >
                            送信
                        </Button>
                    </div>
                </form>
            </div>
            <div className="m-5">
                <Button variant="outline">
                    <Link href="/">戻る</Link>
                </Button>
            </div>
        </div>
    );
};

export default page;

記事を投稿する画面です。よくあるリファレンスそのまんまに近い、簡単なものです。ので解説は不要かと・・・

その他は本筋ではないため、割愛。GITを参照してください。

おわりに

挙動も特段特筆すべきものはありませんので、省略。改めてgitリンクおいておきますので、気になる人は覗いてみてください。引き続きpleasnaterClient、機能拡張していきたいと思います。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?