TERUSIの技術勉強まとめ(コーディング編)

todoのリスト部分の画面作成

作成日: 2023-10-20T13:03:46.753Z

はじめに

前回はtodoの投稿部分を作成しました。
編集と削除の部分を作成する前にある程度画面を作成してしまおうと思います。
間が空いてしまったので抜けている部分があるかもしれないのでそこはご了承ください。

完成イメージ


未完了の部分と

完了部分に分けている

実装開始

1 文言ファイル


todo:{
        title:"TODO APP",
        description:"自分だけのTODOを作成しよう!",
        greeting:" Hello!!!!!",
        notLogin:"ログインしてから来いやー",
        buttonTitle:"登録",
        attribute:{
            todo:{
                labelName:"todo",
            },
        },
        logingMsg:"Loding...",
        complete:"完了タスク",
        imcomplete:"未完了タスク",
        notItem:"アイテムはありません",
        numOfPieces:"個"
}

2 型定義ファイルの設定

toggleの属性の型を保存しておきます。

//todoToggleの型
export type todoToggleType={
    value:string;
    content:string;
}

3 静的データファイル(data.ts)にデータ追加

toggleの属性を保存しておきます。

import { todoToggleType } from "./type";

export const isCmptoggleItems:todoToggleType[]=[
    {
        value:'imcomplete',
        content:'未完了',
    },{
        value:'complete',
        content:"完了"
    }
]

4 todoのmainのファイルであるpage.tsxを修正する

未完了と完了を分けます。
その関係でpage.tsxを編集します。

'use client'
import { ReadonlyURLSearchParams, useSearchParams } from "next/navigation";
import Title from "../components/title";
import Greeting from "./greeting";
import styles from "./style.css";
import NotLogin from "./notLogin";
import TodoInputForm from "./todoInputForm";
import TodoList from "./todoList";
import { useState } from "react";
import { todoType } from "@/shared/type";
import { isCmptoggleItems } from "@/shared/data";
import IsCompleteToggle from "./isCompleteToggle";
const Todo = () => {
    const searchParams:ReadonlyURLSearchParams=useSearchParams();
    const name:string|null=searchParams.get('name');
    const id:string|null=searchParams.get('id');
    const [todo,setTodo]=useState<todoType[]>([]);
    const [imcompleteData,setImcompleteData]=useState<todoType[]>([]);//追加
    const [completeData,setCompleteData]=useState<todoType[]>([]);//追加
    const [toggleItem,setToggleItem]=useState<string>(isCmptoggleItems[0].value);//追加
    return (
        <main className={styles.containar}>
            {id&&name?(
                <>
                    <Title/>
                    <Greeting
                        name={name}
                    />
                    <TodoInputForm
                        id={id}
                        setTodos={setTodo}
                        setCompleteData={setCompleteData}
                        setImcompleteData={setImcompleteData}
                    />
                    <IsCompleteToggle 
                        toggle={toggleItem} 
                        setToggle={setToggleItem}
                    />
                    <TodoList
                        id={id}
                        todos={todo}
                        setTodo={setTodo}
                        completeData={completeData}
                        imcompleteData={imcompleteData}
                        setCompleteData={setCompleteData}
                        setImcompleteData={setImcompleteData}
                        toggle={toggleItem}
                    />
                </>
            ):(
                <NotLogin/>
            )}
        </main>
    );
}

export default Todo;

未完了と完了をそれぞれ保存するuseStateの部分とtoggleの属性を保存しておくuseStateを追加しています。
さらに、これから作成するtoggleのcomponentを宣言しておりそこにtoggleの属性を保存するuseStateをpropsとして宣言します。
TodoList、TodoInputFormに未完了と完了の状態を保存するものをpropsに追加しています。
page.tsxはエラーだらけなはずですが、一旦無視して次へ行きましょう!

5 todoInputFormの編集

「todoInputForm」のフォルダの中のコードを直していきましょう。
直すのはonClick関数の部分ですが全体像を一応載っけておきます。

'use client'

import ja from "@/shared/ja";
import { todoType } from "@/shared/type";
import { Dispatch, SetStateAction, useState } from "react";
import InputForm from "../inputForm";
import PostButton from "../postButton";
import { v4 } from "uuid";
import { postTodo } from "@/api/todo";
interface props{
    id:string;
    setTodos:Dispatch<SetStateAction<todoType[]>>;
    setImcompleteData:Dispatch<SetStateAction<todoType[]>>;
    setCompleteData:Dispatch<SetStateAction<todoType[]>>;
}
const TodoInputForm = ({
    id,
    setTodos,
    setCompleteData,
    setImcompleteData,
}:props) => {
    const [todo,setTodo]=useState<string>("");
    const onClick=async()=>{
        const uuid:string=v4();
        const setData:todoType={
            id:uuid,
            todo:todo,
            checked:0,
        }
        const data:todoType[]=await postTodo(setData,id);
        setTodos(data);
        setTodo("");
        setImcompleteData(data.filter((item:todoType)=>item.checked===0));//追加
        setCompleteData(data.filter((item:todoType)=>item.checked===1));//追加
    };
    return (
        <div>
            <div>
                <InputForm
                    labelName={ja.todo.attribute.todo.labelName}
                    value={todo}
                    setValue={setTodo}
                />
            </div>
            <div>
                <PostButton
                    clickEvent={onClick}
                    buttonTitle={ja.todo.buttonTitle}
                    isDisabled={todo?false:true}
                />
            </div>
        </div>
    );
}

export default TodoInputForm;

新しく出てきたメソッドがありますね。
filterが登場です。これは条件に合ったものをリストに入れていくものになります。
完了は1で未完了は0なのでそれぞれ、その条件を利用して完了と未完了を分けます。

6 todoListの編集

このフォルダを編集していきます。
編集部分が多いですが、一旦コードを載せます。
エラーが多くなると思いますが一旦無視しましょう。

'use client'
import { getTodo } from "@/api/todo";
import ja from "@/shared/ja";
import { todoToggleType, todoType } from "@/shared/type";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import Imcomplete from "../isComplete";
import styles from "./style.css";
import TodoItem from "../todoItem";
import { isCmptoggleItems } from "@/shared/data";
import IsComplete from "../isComplete";
interface props{
    id:string|null;
    todos:todoType[];
    setTodo:Dispatch<SetStateAction<todoType[]>>;
    imcompleteData:todoType[];
    setImcompleteData:Dispatch<SetStateAction<todoType[]>>;
    completeData:todoType[];
    setCompleteData:Dispatch<SetStateAction<todoType[]>>;
    toggle:string;
}
const TodoList = ({
    id,
    todos,
    setTodo,
    completeData,
    imcompleteData,
    setCompleteData,
    setImcompleteData,
    toggle
}:props) => {
    const [loadingMsg,setLodingMsg]=useState<string>(ja.todo.logingMsg);
    const firstFunction=async(userId:string)=>{
        const data:todoType[]=await getTodo(userId);
        setTodo(data);
        setImcompleteData(data.filter((item:todoType)=>item.checked===0));
        setCompleteData(data.filter((item:todoType)=>item.checked===1));
    }
    useEffect(()=>{
        if(id){
            firstFunction(id);
            setLodingMsg("");
        }else{
            setLodingMsg(ja.todo.logingMsg)
        }
    },[id])
    return (
        <>
            {loadingMsg?(
                <div>
                    <p>{loadingMsg}</p>
                </div>
            ):toggle===isCmptoggleItems[0].value?(
                <div className={styles.containar}>
                    <IsComplete
                        title={ja.todo.imcomplete}
                        isComplete={false}
                        numOfimCmp={imcompleteData.length}
                    >
                        {imcompleteData.map((item:todoType)=>(
                            <div key={item.id}>
                                <TodoItem 
                                    todo={item}
                                />
                            </div>
                        ))}
                    </IsComplete>
                </div>
            ):(
                <div className={styles.containar}>
                    <IsComplete
                        title={ja.todo.complete}
                        isComplete={true}
                        numOfimCmp={completeData.length}
                    >
                        {completeData.map((item:todoType)=>(
                            <div key={item.id}>
                                <TodoItem 
                                    todo={item}
                                />
                            </div>
                        ))}
                    </IsComplete>
                </div>
            )}
        </>
    );
}

export default TodoList;

ここでは全てのtodoを取得する関数firstFunctionに完了タスクと未完了タスクを分けるコードを追加しています。
また、描画の部分では三項演算子とtoggleの属性をうまいこと使って完了部分と未完了部分で分けます。
そしてまだ、作成していないがIsCompleteコンポーネントに「完了タスク」か「未完了タスク」かのタイトル、完了しているかしていないかのboolean、お互いの配列の長さをpropsにしている。

7 isCompleteを作成

上記の名前のフォルダを作りそこにコードを書いていきましょう。

import ja from "@/shared/ja";
import { ReactNode } from "react";
import styles from "./style.css";

interface props{
    title:string;
    isComplete:boolean;
    numOfimCmp:number;
    children:ReactNode[];
}
const IsComplete = ({title,isComplete,numOfimCmp,children}:props) => {
    return (
        <div className={styles.containar}>
            <div>
                <h2>{title}</h2>
            </div>
            <div>
                <p>{`${numOfimCmp}${ja.todo.numOfPieces}`}</p>
            </div>
            <div className={isComplete?styles.completeBox:styles.imcompleteBox}>
            {children.map((item:ReactNode,index:number)=>(
                    <div key={index}>
                        {item}
                    </div>
                ))}
            </div>
        </div>
    );
}

export default IsComplete;

新しく出てきたものがまたあります。Reactnodeのchildrenです。todoListの部分でいつもとcomponentの宣言が違いましたよね。
componentはhtmlタグ自体をpropsとして渡すことができます。複数のタグを渡しているので配列の型になっています。
この部分はstyleを振る必要があるのでstylesheetも書いていきましょう。

import { style } from "@vanilla-extract/css";

const styles={
    containar:style({
        margin:"5px auto",
        width:"245px",
        '@media':{
            'screen and (max-width:350px)':{
                width:"100%",
            }
        }
    }),
    imcompleteBox:style({
        backgroundColor:"#FF00FF",
        maxHeight:"500px",
        overflowY:"auto",
    }),
    completeBox:style({
        backgroundColor:"",
        maxHeight:"500px",
        overflowY:"auto",
    })
}

export default styles;

完了と未完了で背景色を変えているのでその辺のコードも注目しながら見てください。

7 todoItemの作成

上記フォルダを作成し書いていきます。

import { todoType } from "@/shared/type";
import styles from "./style.css";
import EditButton from "../editButton";
import CheckButton from "../checkButton";
import DeleteButton from "../deleteButton";

interface props{
    todo:todoType;
}
const TodoItem = ({todo}:props) => {
    return (
        <div className={styles.itemBox}>
            <div className={styles.textbox}>
                {todo.todo}
            </div>
            <div className={styles.editButtons}>
                <EditButton/>
                <CheckButton/>
                <DeleteButton/>
            </div>
        </div>
    );
}

export default TodoItem;

また、componentがありますね。
その前にstylesheetを書きます。

import { style } from "@vanilla-extract/css";

const styles={
    itemBox:style({
        height:"50px",
        backgroundColor:"white",
        display:"flex",
        alignItems:"center",
        margin:"5px"
    }),
    textbox:style({
        width:"50%",
        textAlign:"start",
    }),
    editButtons:style({
        display:"flex",
    })
}

export default styles;

stylesheetに関してはあまり説明はしません。
では3つのcomponentを作っていきましょう。

8 editButton、checkButton、deleteButtonの作成

3つのcomponentを作成していきます。基本的にはmuiを使っていきます。
まず、EditButtonから。

import EditRoundedIcon from '@mui/icons-material/EditRounded';
import { IconButton } from '@mui/material';
const EditButton = () => {
    return (
        <div>
            <IconButton aria-label="edit">
                <EditRoundedIcon />
            </IconButton>
        </div>
    );
}

export default EditButton;

次にCheckButton。

import { Checkbox } from "@mui/material";

const CheckButton = () => {
    return (
        <div>
            <Checkbox/>
        </div>
    );
}

export default CheckButton;

最後にDeleteButton

import { IconButton } from "@mui/material";
import DeleteIcon from '@mui/icons-material/Delete';
const DeleteButton = () => {
    return (
        <div>
            <IconButton aria-label="delete">
                <DeleteIcon />
            </IconButton>
        </div>
    );
}

export default DeleteButton;

9 toggleボタンを作る

あと一息です。頑張りましょう。
最後にtoggleボタンを作ります。
これもmuiの素材をパクってきます。
「isCompleteToggle」フォルダを作成して書いていきます。

import { isCmptoggleItems } from "@/shared/data";
import { todoToggleType } from "@/shared/type";
import { ToggleButton, ToggleButtonGroup } from "@mui/material";
import { Dispatch, SetStateAction,MouseEvent } from "react";
interface props{
    toggle:string;
    setToggle:Dispatch<SetStateAction<string>>;
}
const IsCompleteToggle = ({toggle,setToggle}:props) => {
    const handleChange = (
        event: MouseEvent<HTMLElement>,
        newAlignment: string,
    ) => {
        setToggle(newAlignment);
    };
    
    return (
        <ToggleButtonGroup
            color="primary"
            value={toggle}
            exclusive
            onChange={handleChange}
            aria-label="Platform"
        >
            {isCmptoggleItems.map((item:todoToggleType,index:number)=>(
                <ToggleButton 
                    key={index} 
                    value={item.value}
                >
                    {item.content}
                </ToggleButton>
            ))}
        </ToggleButtonGroup>
    );
}

export default IsCompleteToggle;

toggleの属性を定義した配列オブジェクトとmap関数を利用してtoggleボタンを作成しています。
また、toggleの状態が変わった時にその属性を入れ替えるようhandleChange関数で処理を書いています。
これで画面が出来上がったはずです。(抜けていたらごめんなさい。)

次回

編集の処理