見出し画像

Next.js とSupabaseで求人マッチングアプリを作る⑭~ステータス機能の実装~

前回に続いて、今回は面接前~内定承諾までのステータスを変更する機能を作成します。

この機能は、企業側が各応募者とどこまでやり取りを進めていたか、わかりやすくするものでもありますし、応募者としても進捗が可視化されて安心することができます。
また今回は取り扱いませんが、一定期間ステータスが変化しなかった場合、行動を促したり、一定のステータスに達した案件について成功報酬を取る…といったことも可能です。

では早速作り方を見ていきましょう。

Next.jsの実装

チャットページの親側でユーザタイプを定義し、子コンポーネントに渡すことができるようにしました。

app/chats/page.tsx

import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
import ChatView from "@/components/chats/ChatView";
import {getUserTypeFromEnv} from "@/utils/usertype";

export default async function ChatsPage({
                                                    searchParams,
                                                }: {
    searchParams: { companyid: string };
}) {
    const supabase = createClient();
    const usertype = getUserTypeFromEnv()

    const {
        data: { user },
    } = await supabase.auth.getUser();

    if (!user) {
        return redirect("/login");
    }

    return (
        <div className="flex-1 w-full flex flex-col items-center">
            <Header />
            <ChatView usertype={usertype}></ChatView>
        </div>
    );
}

components/Header.tsx

今回の本題とは関係ありませんが、基本情報がない状態で応募やスカウト、求人の作成ができないようにヘッダーのだし分けを行っています。

import {createClient} from "@/utils/supabase/server";
import Link from "next/link";
import {redirect} from "next/navigation";
import {getUserTypeFromEnv, userType} from "@/utils/usertype";
import HeaderSearchForm from "@/components/jobseeker/HeaderSearchForm";
import Logo from "@/components/Logo";

export default async function Header() {
    const supabase = createClient();

    const usertype = getUserTypeFromEnv();

    let userDataFlg = false;

    const {
        data: {user},
    } = await supabase.auth.getUser();

    const isUserDataExist = async () => {
        if (usertype == userType.company) {
            const {data, error } = await supabase.from("mst_company").select().eq("user_uid", user?.id)
            if (data?.length ? data?.length : 0 > 0) {
                userDataFlg = true
            }
        } else if (usertype == userType.job_seeker) {
            const {data, error } = await supabase.from("mst_job_seeker").select().eq("user_uid", user?.id)
            if (data?.length ? data?.length : 0 > 0) {
                userDataFlg = true
            }
        }
    }

    await isUserDataExist()

    const signOut = async () => {
        "use server";

        const supabase = createClient();
        await supabase.auth.signOut();
        return redirect("/login");
    };

    return (
        <nav className="w-full p-3 text-sm border-b border-gray-300 bg-white">
            <Logo></Logo>
            {user && usertype == userType.company ? (
                <div className="flex justify-end items-center gap-4">
                    {userDataFlg ? (<>
                        <Link className="text-center w-20" href="/myjoblist">
                            <div className="block">
                            <span className="material-symbols-outlined">
                                work
                            </span>
                            </div>
                            自社の求人
                        </Link>
                        <Link className="text-center w-20" href="/jobseekerlist">
                            <div className="block">
                                <span className="material-symbols-outlined">
                                    badge
                                </span>
                            </div>
                            求職者一覧
                        </Link>
                        <Link className="text-center w-20" href="/chats">
                            <div className="block">
                                <span className="material-symbols-outlined">
                                    chat
                                </span>
                            </div>
                            メッセージ
                        </Link>
                    </>) : null}
                    <Link className="text-center w-20" href="/userpage">
                        <div className="block">
                                <span className="material-symbols-outlined">
                                    person
                                </span>
                        </div>
                        ユーザ
                    </Link>
                    <form className="content-center" action={signOut}>
                        <button
                            className="w-24 py-2 px-4 rounded-md no-underline bg-btn-background hover:bg-btn-background-hover">
                            ログアウト
                        </button>
                    </form>
                </div>
            ) : user && usertype == userType.job_seeker ? (
                <div className="w-full flex justify-between gap-4">
                    <HeaderSearchForm></HeaderSearchForm>
                    <div className="flex">
                        {userDataFlg ? (<>
                            <Link className="text-center w-20" href="/joblist">
                                <div className="block">
                                <span className="material-symbols-outlined">
                                    work
                                </span>
                                </div>
                                求人一覧
                            </Link>
                            <Link className="text-center w-20" href="/companylist">
                                <div className="block">
                                <span className="material-symbols-outlined">
                                    apartment
                                </span>
                                </div>
                                企業一覧
                            </Link>
                            <Link className="text-center w-20" href="/chats">
                                <div className="block">
                                <span className="material-symbols-outlined">
                                    chat
                                </span>
                                </div>
                                メッセージ
                            </Link>
                        </>) : null}

                        <Link className="text-center w-20" href="/userpage">
                            <div className="block">
                                <span className="material-symbols-outlined">
                                    person
                                </span>
                            </div>
                            ユーザ
                        </Link>
                        <form className="content-center" action={signOut}>
                            <button
                                className="w-24 py-2 px-4 rounded-md no-underline bg-btn-background hover:bg-btn-background-hover">
                                ログアウト
                            </button>
                        </form>
                    </div>

                </div>
            ) : (
                <div className="flex justify-end items-center gap-4">
                    <Link
                        href="/login"
                        className="w-24 py-2 px-4 rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
                    >
                        ログイン
                    </Link>
                    <Link
                        href="/signup"
                        className="w-24 py-2 px-4 rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
                    >
                        会員登録
                    </Link>
                </div>
            )}
        </nav>
    );
}

components/chats/ChatList.tsx

自分と相手のやり取りがわかりやすいよう、CSSを修正しています。

"use client"
import {createClient} from "@/utils/supabase/client";
import {useEffect, useState} from "react";
import {User} from "@supabase/gotrue-js";
import {Database} from "@/types/supabase";
import SendButton from "@/components/chats/SendButton";

type Props = {
    user: User | null,
    job_seeker_jobid: number | null
}
export default function ChatList({user, job_seeker_jobid}: Props) {
    const supabase = createClient()
    const [chatData, setChatData] = useState<Database["public"]["Tables"]["trn_apply_message"]["Row"][]>([]);
    // リロードのためにトグルするやつ。
    const [reloadFlg, setReloadFlg] = useState<boolean>(false);

    useEffect(() => {
        if (job_seeker_jobid == null || user == null) return
        getChatData()
        setReloadFlg(false)
    }, [job_seeker_jobid, reloadFlg]);

    const getChatData = async () => {
        const {data, error} = await supabase.from("trn_apply_message").select().eq("job_seeker_job_id", job_seeker_jobid)

        if (error) {
            console.log(error);
            return []
        }

        setChatData(data)
    }

    return (<div>
        <ul>
            {chatData.map((item) => (
                <li className={user?.id == item.sender_id ? ("flex justify-end mb-2") : ("flex mb-2")} key={item.id}>
                    <div className={user?.id == item.sender_id ? ("ml-4 inline-block rounded-md p-2 bg-green-500 text-white") : ("mr-4 inline-block rounded-md p-2 bg-white")}>
                        {item.message}
                    </div>
                </li>
            ))}
        </ul>
        <SendButton user={user} job_seeker_jobid={job_seeker_jobid} setReloadFlg={setReloadFlg}></SendButton>
    </div>)
}

components/chats/ChatView.tsx

企業側のみステータスの変更ができるよう、変更しています。
また、UI上の変更としてチャットのサイドメニューに求人の名前と求職者の名前、年齢を表示しました。

"use client"

import {useEffect, useState} from "react";
import {createClient} from "@/utils/supabase/client";
import {getUserTypeFromEnv, userType} from "@/utils/usertype";
import {Database} from "@/types/supabase";
import ChatList from "@/components/chats/ChatList";
import {User} from "@supabase/gotrue-js";
import {getYearsOld} from "@/utils/jobseekerUtils";
import {DatabaseType} from "@/utils/DatabaseType";

type Props = {
    usertype: userType
}
export default function ChatView({usertype}: Props) {
    const supabase = createClient();
    const [jobSeekerJobs, setJobSeekerJobs] = useState<any[]>([]);
    const [currentJobSeekerJobID, setCurrentJobSeekerJobID] = useState<number | null>(null);
    const [currentUser, setCurrentUser] = useState<User | null>(null);
    useEffect(() => {
        getJobSeekerJob()
    }, []);

    // ユーザと結びつくjob_seeker_job IDを検索
    const getJobSeekerJob = async () => {
        let tmpUser: User | null
        if (currentUser) {
            tmpUser = currentUser
        } else {
            const {data: {user}} = await supabase.auth.getUser()
            setCurrentUser(user)
            tmpUser = user
        }

        const usertype = getUserTypeFromEnv()
        let fixedJobList: Database["public"]["Tables"]["m2m_job_seeker_job"]["Row"][] = []
        let result: any[] = []
        if (usertype == userType.job_seeker && tmpUser != null) {
            const {data, error} = await supabase.from("m2m_job_seeker_job").select().eq("job_seeker_id", tmpUser.id)
            if (error) return;

            fixedJobList = data as Database["public"]["Tables"]["m2m_job_seeker_job"]["Row"][]

        } else if (usertype == userType.company && tmpUser != null) {
            const jobList = await getJobListFromCompanyID(tmpUser.id)
            for (let i = 0; i < jobList.length; i++) {
                const {data, error} = await supabase.from("m2m_job_seeker_job").select().eq("job_id", jobList[i]["id"])
                if (error) continue;

                fixedJobList = fixedJobList.concat(data as Database["public"]["Tables"]["m2m_job_seeker_job"]["Row"][]);
            }
        }

        // 求人名、求職者名取得
        for (let i = 0; i < fixedJobList.length; i++) {
            const jobList = await getJobListFromJobID(fixedJobList[i]["job_id"])
            if (jobList.length == 0) continue

            const {
                data,
                error
            } = await supabase.from("mst_job_seeker").select().eq("user_uid", fixedJobList[i]["job_seeker_id"])
            if (error || data == null || data.length == 0) continue

            const tmp_data = data as Database["public"]["Tables"]["mst_job_seeker"]["Row"][]

            const yearsOld = getYearsOld(tmp_data[0]["birthday"])

            let statusStr = ""

            switch (fixedJobList[i]["status"]) {
                case "before_interview":
                    statusStr = "面接前"
                    break;
                case "reject":
                    statusStr = "不採用"
                    break;
                case "selection":
                    statusStr = "選考中"
                    break;
                case "offer":
                    statusStr = "オファー"
                    break;
                case "decline":
                    statusStr = "選考辞退"
                    break
                case "accept":
                    statusStr = "内定承諾"
                    break
            }

            result.push({
                id: fixedJobList[i]["id"],
                jobName: jobList[0]["name"],
                username: tmp_data[0]["last_name"] + " " + tmp_data[0]["first_name"],
                yearsOld: yearsOld,
                status: statusStr
            })
        }

        setJobSeekerJobs(result)
    }

    const getJobListFromJobID = async (id: string) => {
        const {data, error} = await supabase.from("trn_job").select().eq("id", id)
        if (error) return []

        return data as Database["public"]["Tables"]["trn_job"]["Row"][]
    }

    const getJobListFromCompanyID = async (companyId: string) => {
        const {data, error} = await supabase.from("trn_job").select().eq("company_uid", companyId)

        if (error) return []
        return data as Database["public"]["Tables"]["trn_job"]["Row"][]
    }

    const [showStatusModal, setShowStatusModal] = useState<boolean>(false)
    const [currentStatus, setCurrentStatus] = useState<string>("")
    const handleStatus = async (id: string) => {
        const result = window.confirm("この求職者のステータスを変更しますか?");
        if (!result) return

        setShowStatusModal(false)

        const {error} = await supabase.from("m2m_job_seeker_job").update({status: currentStatus}).eq("id", id)
        if (error) return

        await getJobSeekerJob()
    }

    return (<div className="relative flex w-full max-w-4xl h-max min-h-screen pb-16">
        <ul className="w-3/5 bg-white">
            {jobSeekerJobs.map((item) => (
                <li className="relative border-b-gray-200 border-b" key={item.id}>
                    <button onClick={() => {
                        setCurrentJobSeekerJobID(item.id)
                    }}
                            className="relative text-left p-4 bg-white w-full hover:bg-gray-200 flex items-center justify-between">
                        <div className="w-48 font-bold text-lg">{item.jobName}<br/><span
                            className="font-normal text-base">{item.username} {item.yearsOld}歳</span></div>
                        {usertype == userType.job_seeker ? (<div
                            className="relative w-16 bg-gray-300 rounded-md text-center">{item.status}</div>) : usertype == userType.company ? (
                            <button onClick={() => setShowStatusModal(true)}
                                    className="relative w-16 bg-gray-300 rounded-md text-center hover:bg-gray-50">{item.status}</button>) : null}
                    </button>
                    {showStatusModal ? (
                        <div className="absolute top-0 right-0 p-4 z-50 border border-gray-300 bg-gray-100">
                            <div className="text-right">
                                <button onClick={() => setShowStatusModal(false)}>
                                <span className="material-symbols-outlined">
                                    close
                                </span>
                                </button>
                            </div>
                            <p>ステータスを選択してください。</p>
                            <select className="border block mb-4"
                                    onChange={(e) => setCurrentStatus(e.target.value)}
                            >
                                <option value="before_interview" selected={item.status === "面接前"}>面接前</option>
                                <option value="reject" selected={item.status === "不採用"}>不採用</option>
                                <option value="selection" selected={item.status === "選考中"}>選考中</option>
                                <option value="offer" selected={item.status === "オファー"}>オファー</option>
                                <option value="decline" selected={item.status === "選考辞退"}>選考辞退</option>
                                <option value="accept" selected={item.status === "内定承諾"}>内定承諾</option>
                            </select>
                            <div className="w-full text-right">
                                <button className="bg-green-500 hover:bg-green-600 rounded-md px-4 py-2 text-white"
                                        onClick={() => handleStatus(item.id)}>保存
                                </button>
                            </div>
                        </div>) : null}
                </li>
            ))}
        </ul>
        <div className="w-full bg-gray-200 border-l p-4">
            <ChatList user={currentUser} job_seeker_jobid={currentJobSeekerJobID}></ChatList>
        </div>
    </div>)
}

utils/jobseekerUtils.ts

新規でutilファイルを作成し、誕生日から年齢を計算する処理を共通化しました。

export const getYearsOld = (birthday: string) => {
    const dateArr = birthday.split("-");
    const today = new Date();
    let age = today.getFullYear() - parseInt(dateArr[0]);
    const thisYearsBirthday = new Date(today.getFullYear(), parseInt(dateArr[1]) - 1, parseInt(dateArr[2]))

    if(today < thisYearsBirthday){
        //誕生日の調整
        age--;
    }
    return age;
}

components/company/JobSeekerDataUtil.ts

上記の処理がもともとあった`JobSeekerDataUtil.ts`から該当部分を削除しています。

"use client"

import {Database} from "@/types/supabase";
import {createClient} from "@/utils/supabase/client";
import {getYearsOld} from "@/utils/jobseekerUtils";

export async function getFixedJobSeekerData(data: Database["public"]["Tables"]["mst_job_seeker"]["Row"][]) {
    const supabase = createClient();
    const getNationalityData = async () => {
        const { data, error } = await supabase.from("mst_nationality").select();
        if (error) {
            console.log(error);
            return [];
        }

        return data as Database["public"]["Tables"]["mst_nationality"]["Row"][]
    }

    const getWorkLocationData = async () => {
        const {data, error} = await supabase.from("mst_work_location").select();
        if (error) {
            console.log(error);
            return [];
        }

        return data as Database["public"]["Tables"]["mst_work_location"]["Row"][];
    };

    const result: any[] = []
    const nationalityData = await getNationalityData();
    const workLocationData = await getWorkLocationData();

    for (let i = 0; i < data.length; i++) {
        const age = getYearsOld(data[i]["birthday"])

        let nationalityStr = ""
        for (let j = 0; j < nationalityData.length; j++) {
            const nationalityId = data[i]["nationality_id"]
            if (nationalityId != null) {
                if (data[i]["nationality_id"]! === nationalityData[j]["id"]) {
                    nationalityStr = nationalityData[j]["nationality"]
                }
            }
        }

        let workLocationStr = ""
        for (let j = 0; j < workLocationData.length; j++) {
            const workLocationId = data[i]["desired_work_location"]
            if (workLocationId != null) {
                if (data[i]["desired_work_location"]! === workLocationData[j]["id"]) {
                    workLocationStr = workLocationData[j]["work_location"]
                }
            }
        }

        result.push({
            "user_uid": data[i]["user_uid"],
            "gender": data[i]["gender"],
            "age": age,
            "nationality": nationalityStr,
            "desired_annual_income": data[i]["desired_annual_income"],
            "desired_change_job_date": data[i]["desired_change_job_date"],
            "residence_qualification_expired": data[i]["residence qualification_expired"],
            "workLocation": workLocationStr
        })
    }

    return result
}

components/userinfo/CompanyBasic.tsx

基本情報の電話番号とメールアドレスは重複してはいけないのですが、現状のエラーメッセージだとなぜ保存に失敗したのかわからないため修正しました。

"use client";
import {createClient} from "@/utils/supabase/client";
import {useEffect, useState} from "react";
import {redirect} from "next/navigation";
import {SubmitButton} from "@/components/SubmitButton";
import {Database} from "@/types/supabase";
import DefaultInput from "@/components/common/DefaultInput";
import {InputType} from "@/utils/inputType";
import DefaultSelect from "@/components/common/DefaultSelect";
import {DatabaseType} from "@/utils/DatabaseType";

export default function CompanyBasic() {
    const [message, setMessage] = useState("");
    const supabase = createClient();
    const [industryData, setIndustryData] = useState<
        Database["public"]["Tables"]["mst_industry"]["Row"][]
    >([]);

    // 初回のデータかどうか?
    const [isFirst, setIsFirst] = useState<boolean>(false);
    // フォームデータを保持する
    const [companyname, setCompanyname] = useState("");
    const [zipcode, setZipcode] = useState("");
    const [address1, setAddress1] = useState("");
    const [address2, setAddress2] = useState("");
    const [phone, setPhone] = useState<string | null>(null);
    const [email, setEmail] = useState("");
    const [companyurl, setCompanyurl] = useState<string | null>(null);
    const [capital, setCapital] = useState<number | null>(null);
    const [employee, setEmployee] = useState<number | null>(null);
    const [annual_turnover, setAnnual_turnover] = useState<number | null>(null);
    const [established_at, setEstablishd_at] = useState<string | null>(null);
    const [industry_id_1, setIndustry_id_1] = useState<string | null>(null);
    const [industry_id_2, setIndustry_id_2] = useState<string | null>(null);
    const [industry_id_3, setIndustry_id_3] = useState<string | null>(null);
    const [company_image_url_1, setCompany_image_url_1] = useState<string | null>(
        null
    );
    const [company_image_url_2, setCompany_image_url_2] = useState<string | null>(
        null
    );
    const [company_image_url_3, setCompany_image_url_3] = useState<string | null>(
        null
    );
    const [company_image_url_4, setCompany_image_url_4] = useState<string | null>(
        null
    );
    const [company_image_url_5, setCompany_image_url_5] = useState<string | null>(
        null
    );

    useEffect(() => {
        getIndustyData();
        setData();
    }, []);

    const setData = async () => {
        const {
            data: {user},
        } = await supabase.auth.getUser();

        if (!user) {
            return redirect("/login");
        }

        const {data} = await supabase
            .from("mst_company")
            .select()
            .eq("user_uid", user.id);

        if (data != null && data?.length != 0) {
            const company_data: Database["public"]["Tables"]["mst_company"]["Row"] =
                data[0];
            setCompanyname(company_data.name);
            setZipcode(company_data.zipcode);
            setAddress1(company_data.address1);
            setAddress2(company_data.address2);
            if (company_data.phone != null) {
                setPhone(company_data.phone);
            }
            setEmail(company_data.email);
            if (company_data.url != null) {
                setCompanyurl(company_data.url);
            }
            if (company_data.capital != null) {
                setCapital(company_data.capital);
            }
            if (company_data.employee != null) {
                setEmployee(company_data.employee);
            }
            if (company_data.annual_turnover != null) {
                setAnnual_turnover(company_data.annual_turnover);
            }
            if (company_data.established_at != null) {
                setEstablishd_at(company_data.established_at);
            }
            if (company_data.industry_id_1 != null) {
                setIndustry_id_1(company_data.industry_id_1);
            }
            if (company_data.industry_id_2 != null) {
                setIndustry_id_2(company_data.industry_id_2);
            }
            if (company_data.industry_id_3 != null) {
                setIndustry_id_3(company_data.industry_id_3);
            }
            if (company_data.company_image_url_1 != null) {
                setCompany_image_url_1(company_data.company_image_url_1);
            }
            if (company_data.company_image_url_2 != null) {
                setCompany_image_url_2(company_data.company_image_url_2);
            }
            if (company_data.company_image_url_3 != null) {
                setCompany_image_url_3(company_data.company_image_url_3);
            }
            if (company_data.company_image_url_4 != null) {
                setCompany_image_url_4(company_data.company_image_url_4);
            }
            if (company_data.company_image_url_5 != null) {
                setCompany_image_url_5(company_data.company_image_url_5);
            }
        } else {
            setIsFirst(true);
            setEmail(user.email!);
        }
    };

    const getIndustyData = async () => {
        const {data, error} = await supabase.from("mst_industry").select();
        if (error) {
            console.log(error);
            return;
        }

        const fixed_data: Database["public"]["Tables"]["mst_industry"]["Row"][] =
            data;
        setIndustryData(fixed_data);
    };

    const updateEmail = async () => {
        const {data, error} = await supabase.auth.updateUser({
            email: email,
        });

        console.log(error);
        if (error) {
            return;
        }
        console.log(data);
    };

    const onSubmit = async () => {
        const {
            data: {user},
        } = await supabase.auth.getUser();
        if (!user) {
            return redirect("/login");
        }

        if (user.email != email) {
            await updateEmail();
        }

        const timeStamp = new Date().toISOString();

        const {error} = await supabase.from("mst_company").upsert({
            user_uid: user.id,
            name: companyname,
            zipcode: zipcode,
            address1: address1,
            address2: address2,
            phone: phone,
            email: email,
            url: companyurl,
            capital: capital,
            employee: employee,
            annual_turnover: annual_turnover,
            established_at: established_at,
            industry_id_1: industry_id_1,
            industry_id_2: industry_id_2,
            industry_id_3: industry_id_3,
            company_image_url_1: company_image_url_1,
            company_image_url_2: company_image_url_2,
            company_image_url_3: company_image_url_3,
            company_image_url_4: company_image_url_4,
            company_image_url_5: company_image_url_5,
            updated_at: timeStamp,
        });

        if (error) {
            if (error.message === "duplicate key value violates unique constraint \"mst_job_seeker_email1_key\"") {
                setMessage("保存に失敗しました。ご利用のメールアドレスは既に登録されています。")
                return;
            }

            if (error.message === "duplicate key value violates unique constraint \"mst_job_seeker_phone_key\"") {
                setMessage("保存に失敗しました。ご利用の電話番号は既に登録されています。")
                return;
            }

            setMessage("保存に失敗しました。");
            return;
        } else {
            setMessage("");
        }
        return redirect("/userpage");
    };

    return (
        <div className="flex-1 flex flex-col w-96 py-8 max-w-xl justify-center gap-2">
            <form className="animate-in flex-1 flex flex-col w-full justify-center gap-2 text-foreground">
                <h2>企業の登録情報</h2>
                {isFirst ? (
                    <>
                        <DefaultInput name={"companyname"} value={companyname} setter={setCompanyname} isRequired={true}
                                      labelText={"会社名*"} placeholderText={"株式会社○○"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"zipcode"} value={zipcode} setter={setZipcode} isRequired={true}
                                      labelText={"郵便番号*"}
                                      placeholderText={"0000000 ※ハイフンなしでご入力ください"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"address1"} value={address1} setter={setAddress1} isRequired={true}
                                      labelText={"住所1*"}
                                      placeholderText={"東京都港区浜松町2丁目2番15号"} inputType={InputType.text}/>
                        <DefaultInput name={"address2"} value={address2} setter={setAddress2} isRequired={true}
                                      labelText={"住所2*"} placeholderText={"浜松町ダイヤビル2F"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"email"} value={email != null ? email : undefined} setter={setEmail}
                                      isRequired={true}
                                      labelText={"メールアドレス*"} placeholderText={"email@example.com"}
                                      inputType={InputType.email}/>

                        <DefaultInput name={"employee"} value={employee != null ? employee : undefined}
                                      setter={setEmployee} isRequired={false}
                                      labelText={"従業員数*"} placeholderText={"50"} inputType={InputType.number}/>

                        <DefaultSelect name={"industry_id_1"} value={industry_id_1 ? industry_id_1 : ""}
                                       setter={setIndustry_id_1} isRequired={false} labelText={"業界1*"}
                                       selectData={industryData} databaseType={DatabaseType.mst_industry}/>
                        <DefaultInput name={"established_at"} value={established_at ? established_at : ""}
                                      setter={setEstablishd_at} isRequired={false}
                                      labelText={"設立*"} placeholderText={""} inputType={InputType.date}/>
                    </>
                ) : (
                    <>
                        <DefaultInput name={"companyname"} value={companyname} setter={setCompanyname} isRequired={true}
                                      labelText={"会社名*"} placeholderText={"株式会社○○"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"zipcode"} value={zipcode} setter={setZipcode} isRequired={true}
                                      labelText={"郵便番号*"}
                                      placeholderText={"0000000 ※ハイフンなしでご入力ください"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"address1"} value={address1} setter={setAddress1} isRequired={true}
                                      labelText={"住所1*"}
                                      placeholderText={"東京都港区浜松町2丁目2番15号"} inputType={InputType.text}/>
                        <DefaultInput name={"address2"} value={address2} setter={setAddress2} isRequired={true}
                                      labelText={"住所2*"} placeholderText={"浜松町ダイヤビル2F"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"phone"} value={phone != null ? phone : undefined} setter={setPhone}
                                      isRequired={false}
                                      labelText={"電話番号"}
                                      placeholderText={"00000000000 ※ハイフンなしでご入力ください"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"email"} value={email != null ? email : undefined} setter={setEmail}
                                      isRequired={true}
                                      labelText={"メールアドレス*"} placeholderText={"email@example.com"}
                                      inputType={InputType.email}/>
                        <DefaultInput name={"companyurl"} value={companyurl != null ? companyurl : undefined}
                                      setter={setCompanyurl} isRequired={false}
                                      labelText={"ホームページURL"} placeholderText={"https://example.com"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"capital"} value={capital != null ? capital : undefined} setter={setCapital}
                                      isRequired={false}
                                      labelText={"資本金"} placeholderText={"100 ※単位は万円"}
                                      inputType={InputType.number}/>
                        <DefaultInput name={"employee"} value={employee != null ? employee : undefined}
                                      setter={setEmployee} isRequired={false}
                                      labelText={"従業員数*"} placeholderText={"50"} inputType={InputType.number}/>
                        <DefaultInput name={"annual_turnover"}
                                      value={annual_turnover != null ? annual_turnover : undefined}
                                      setter={setAnnual_turnover} isRequired={false}
                                      labelText={"年商"} placeholderText={"5000 ※単位は万円"}
                                      inputType={InputType.number}/>
                        <DefaultInput name={"established_at"} value={established_at ? established_at : ""}
                                      setter={setEstablishd_at} isRequired={false}
                                      labelText={"設立*"} placeholderText={""} inputType={InputType.date}/>

                        <DefaultSelect name={"industry_id_1"} value={industry_id_1 ? industry_id_1 : ""}
                                       setter={setIndustry_id_1} isRequired={false} labelText={"業界1*"}
                                       selectData={industryData} databaseType={DatabaseType.mst_industry}/>

                        <DefaultSelect name={"industry_id_2"} value={industry_id_2 ? industry_id_2 : ""}
                                       setter={setIndustry_id_2} isRequired={false} labelText={"業界2"}
                                       selectData={industryData} databaseType={DatabaseType.mst_industry}/>

                        <DefaultSelect name={"industry_id_3"} value={industry_id_3 ? industry_id_3 : ""}
                                       setter={setIndustry_id_3} isRequired={false} labelText={"業界3"}
                                       selectData={industryData} databaseType={DatabaseType.mst_industry}/>

                        {/*<FileUploadInput name={"company_image_url_1"}*/}
                        {/*                 value={company_image_url_1 != null ? company_image_url_1 : undefined}*/}
                        {/*                 isRequired={false}*/}
                        {/*                 labelText={"会社の画像1のURL"} placeholderText={"会社の画像1のURL"}*/}
                        {/*                 inputType={InputType.text} isImage={true} fileSetter={setCompany_image_url_1}/>*/}

                        {/*<FileUploadInput name={"company_image_url_2"}*/}
                        {/*                 value={company_image_url_2 != null ? company_image_url_2 : undefined}*/}
                        {/*                 isRequired={false}*/}
                        {/*                 labelText={"会社の画像2のURL"} placeholderText={"会社の画像2のURL"}*/}
                        {/*                 inputType={InputType.text} isImage={true} fileSetter={setCompany_image_url_2}/>*/}

                        {/*<FileUploadInput name={"company_image_url_3"}*/}
                        {/*                 value={company_image_url_3 != null ? company_image_url_3 : undefined}*/}
                        {/*                 isRequired={false}*/}
                        {/*                 labelText={"会社の画像3のURL"} placeholderText={"会社の画像3のURL"}*/}
                        {/*                 inputType={InputType.text} isImage={true} fileSetter={setCompany_image_url_3}/>*/}
                        {/*<FileUploadInput name={"company_image_url_4"}*/}
                        {/*                 value={company_image_url_4 != null ? company_image_url_4 : undefined}*/}
                        {/*                 isRequired={false}*/}
                        {/*                 labelText={"会社の画像4のURL"} placeholderText={"会社の画像4のURL"}*/}
                        {/*                 inputType={InputType.text} isImage={true} fileSetter={setCompany_image_url_4}/>*/}
                        {/*<FileUploadInput name={"company_image_url_5"}*/}
                        {/*                 value={company_image_url_5 != null ? company_image_url_5 : undefined}*/}
                        {/*                 isRequired={false}*/}
                        {/*                 labelText={"会社の画像5のURL"} placeholderText={"会社の画像5のURL"}*/}
                        {/*                 inputType={InputType.text} isImage={true} fileSetter={setCompany_image_url_5}/>*/}
                    </>
                )}

                <SubmitButton
                    formAction={onSubmit}
                    className="bg-green-500 hover:bg-green-600 rounded-md px-4 py-2 text-white mb-2"
                    pendingText="企業情報更新中..."
                >
                    保存
                </SubmitButton>
                {message !== "" && (
                    <p className="mt-4 p-4 bg-foreground/10 text-foreground text-center">
                        {message}
                    </p>
                )}
            </form>
        </div>
    );
}

components/userinfo/JobSeekerBasic.tsx

JobSeekerBasicも同様に対応します。

"use client";
import {createClient} from "@/utils/supabase/client";
import {useState, useEffect} from "react";
import {redirect} from "next/navigation";
import {SubmitButton} from "@/components/SubmitButton";
import {Database} from "@/types/supabase";
import {InputType} from "@/utils/inputType";
import DefaultInput from "@/components/common/DefaultInput";
import DefaultSelect from "@/components/common/DefaultSelect";
import {DatabaseType} from "@/utils/DatabaseType";
import FileUploadInput from "@/components/common/FileUploadInput";

export default function JobSeekerBasic() {
    const [message, setMessage] = useState("");
    const supabase = createClient();

    // 初回のデータ入力かどうか?
    const [isFirst, setIsFirst] = useState<boolean>(false);
    const [nationalityData, setNationalityData] = useState<
        Database["public"]["Tables"]["mst_nationality"]["Row"][]
    >([]);
    const [occupationData, setOccupationData] = useState<
        Database["public"]["Tables"]["mst_occupation"]["Row"][]
    >([]);
    const [residenceQualificationData, setResidenceQualificationData] = useState<
        Database["public"]["Tables"]["mst_residence_qualification"]["Row"][]
    >([]);
    const [workLocationData, setWorkLocationData] = useState<
        Database["public"]["Tables"]["mst_work_location"]["Row"][]
    >([]);

    // フォームデータを保持する
    const [lastname, setLastname] = useState("");
    const [firstname, setFirstname] = useState("");
    const [middlename, setMiddlename] = useState<string | null>(null);
    const [gender, setGender] = useState("");
    const [birthday, setBirthday] = useState("");
    const [zipcode, setZipcode] = useState<string | null>(null);
    const [address1, setAddress1] = useState<string | null>(null);
    const [address2, setAddress2] = useState<string | null>(null);
    const [phone, setPhone] = useState<string | null>(null);
    const [email, setEmail] = useState("");
    const [nationality_id, setNationality_id] = useState<string>("");
    const [current_annual_income, setCurrent_annual_income] = useState<
        number | null
    >(null);
    const [desired_annual_income, setDesired_annual_income] = useState<
        number
    >(0);
    const [spouse, setSpouse] = useState<string | null>(null);
    const [desired_occupation_id_1, setDesired_occupation_id_1] = useState<
        string | null
    >(null);
    const [desired_occupation_id_2, setDesired_occupation_id_2] = useState<
        string | null
    >(null);
    const [desired_occupation_id_3, setDesired_occupation_id_3] = useState<
        string | null
    >(null);
    const [desired_change_job_date, setDesired_change_job_date] = useState<
        string
    >("");
    const [residence_qualification_id, setResidence_qualification_id] = useState<
        string | null
    >(null);
    const [residence_qualification_expired, setResidence_qualification_expired] =
        useState<string>("");
    const [
        residence_qualification_front_image_url,
        setResidence_qualification_front_image_url,
    ] = useState<string | null>(null);
    const [
        residence_qualification_back_image_url,
        setResidence_qualification_back_image_url,
    ] = useState<string | null>(null);
    const [profile_image_url, setProfile_image_url] = useState<string | null>(
        null
    );
    const [resume_file_url, setResume_file_url] = useState<string | null>(null);
    const [resume_file_name, setResume_file_name] = useState<string | null>(null);
    const [desired_work_location, setDesired_work_location] = useState<string>("");

    useEffect(() => {
        getNationalityData();
        getOccupationData();
        getResidenceQualificationData();
        getWorklocationData();
        setData();
    }, []);

    const updateEmail = async () => {
        const {data, error} = await supabase.auth.updateUser({
            email: email,
        });

        console.log(error);
        if (error) {
            return;
        }
        console.log(data);
    };

    const setData = async () => {
        const {
            data: {user},
        } = await supabase.auth.getUser();

        if (!user) {
            return redirect("/login");
        }

        if (user.email != email) {
            await updateEmail();
        }

        const {data} = await supabase
            .from("mst_job_seeker")
            .select()
            .eq("user_uid", user.id);

        if (data != null && data?.length != 0) {
            const job_seeker_data: Database["public"]["Tables"]["mst_job_seeker"]["Row"] =
                data[0];
            setLastname(job_seeker_data.last_name);
            setFirstname(job_seeker_data.first_name);
            if (job_seeker_data.middle_name != null) {
                setMiddlename(job_seeker_data.middle_name);
            }
            setGender("" + job_seeker_data.gender);
            setBirthday(job_seeker_data.birthday);
            if (job_seeker_data.zipcode != null) {
                setZipcode(job_seeker_data.zipcode);
            }
            if (job_seeker_data.address1 != null) {
                setAddress1(job_seeker_data.address1);
            }
            if (job_seeker_data.address2 != null) {
                setAddress2(job_seeker_data.address2);
            }
            if (job_seeker_data.phone != null) {
                setPhone(job_seeker_data.phone);
            }
            setEmail(job_seeker_data.email);
            setNationality_id(job_seeker_data.nationality_id);

            if (job_seeker_data.current_annual_income != null) {
                setCurrent_annual_income(job_seeker_data.current_annual_income);
            }
            setDesired_annual_income(job_seeker_data.desired_annual_income);

            if (job_seeker_data.spouse != null) {
                setSpouse("" + job_seeker_data.spouse);
            }
            if (job_seeker_data.desired_occupation_id_1 != null) {
                setDesired_occupation_id_1(job_seeker_data.desired_occupation_id_1);
            }
            if (job_seeker_data.desired_occupation_id_2 != null) {
                setDesired_occupation_id_2(job_seeker_data.desired_occupation_id_2);
            }
            if (job_seeker_data.desired_occupation_id_3 != null) {
                setDesired_occupation_id_3(job_seeker_data.desired_occupation_id_3);
            }
            setDesired_change_job_date(job_seeker_data.desired_change_job_date);

            if (job_seeker_data.residence_qualification_id != null) {
                setResidence_qualification_id(
                    job_seeker_data.residence_qualification_id
                );
            }
            setResidence_qualification_expired(
                job_seeker_data["residence qualification_expired"]
            );
            if (job_seeker_data["residence qualification_front_image_url"] != null) {
                setResidence_qualification_front_image_url(
                    job_seeker_data["residence qualification_front_image_url"]
                );
            }
            if (job_seeker_data["residence qualification_back_image_url"] != null) {
                setResidence_qualification_back_image_url(
                    job_seeker_data["residence qualification_back_image_url"]
                );
            }
            if (job_seeker_data.profile_image_url != null) {
                setProfile_image_url(job_seeker_data.profile_image_url);
            }
            if (job_seeker_data.resume_file_url != null) {
                setResume_file_url(job_seeker_data.resume_file_url);
            }
            if (job_seeker_data.resume_file_name != null) {
                setResume_file_name(job_seeker_data.resume_file_name);
            }
            setDesired_work_location(job_seeker_data.desired_work_location);
        } else {
            setIsFirst(true);
            setEmail(user.email!);
        }
    };

    const getNationalityData = async () => {
        const {data, error} = await supabase.from("mst_nationality").select();
        if (error) {
            console.log(error);
            return;
        }

        const fixed_data: Database["public"]["Tables"]["mst_nationality"]["Row"][] =
            data;
        setNationalityData(fixed_data);
    };

    const getWorklocationData = async () => {
        const {data, error} = await supabase.from("mst_work_location").select();
        if (error) {
            console.log(error);
            return;
        }

        const fixed_data: Database["public"]["Tables"]["mst_work_location"]["Row"][] =
            data;
        setWorkLocationData(fixed_data);
    };

    const getOccupationData = async () => {
        const {data, error} = await supabase.from("mst_occupation").select();
        if (error) {
            console.log(error);
            return;
        }

        const fixed_data: Database["public"]["Tables"]["mst_occupation"]["Row"][] =
            data;
        setOccupationData(fixed_data);
    };

    const getResidenceQualificationData = async () => {
        const {data, error} = await supabase
            .from("mst_residence_qualification")
            .select();
        if (error) {
            console.log(error);
            return;
        }

        const fixed_data: Database["public"]["Tables"]["mst_residence_qualification"]["Row"][] =
            data;
        setResidenceQualificationData(fixed_data);
    };

    const onSubmit = async () => {
        const {
            data: {user},
        } = await supabase.auth.getUser();
        if (!user) {
            return redirect("/login");
        }
        const timeStamp = new Date().toISOString();

        const {error} = await supabase.from("mst_job_seeker").upsert({
            user_uid: user.id,
            last_name: lastname,
            first_name: firstname,
            middle_name: middlename,
            gender: parseInt(gender),
            birthday: birthday,
            zipcode: zipcode,
            address1: address1,
            address2: address2,
            phone: phone,
            email: email,
            nationality_id: nationality_id,
            current_annual_income: current_annual_income,
            desired_annual_income: desired_annual_income,
            spouse: spouse,
            desired_occupation_id_1: desired_occupation_id_1,
            desired_occupation_id_2: desired_occupation_id_2,
            desired_occupation_id_3: desired_occupation_id_3,
            desired_change_job_date: desired_change_job_date,
            residence_qualification_id: residence_qualification_id,
            "residence qualification_expired": residence_qualification_expired,
            "residence qualification_front_image_url":
            residence_qualification_front_image_url,
            "residence qualification_back_image_url":
            residence_qualification_back_image_url,
            profile_image_url: profile_image_url,
            resume_file_url: resume_file_url,
            resume_file_name: resume_file_name,
            desired_work_location: desired_work_location,
            updated_at: timeStamp,
        });
        console.log(error);

        if (error) {
            if (error.message === "duplicate key value violates unique constraint \"mst_job_seeker_email1_key\"") {
                setMessage("保存に失敗しました。ご利用のメールアドレスは既に登録されています。")
                return;
            }

            if (error.message === "duplicate key value violates unique constraint \"mst_job_seeker_phone_key\"") {
                setMessage("保存に失敗しました。ご利用の電話番号は既に登録されています。")
                return;
            }

            setMessage("保存に失敗しました。");
            return;
        } else {
            setMessage("");
        }
        return redirect("/userpage");
    };

    return (
        <div className="flex-1 flex flex-col w-96 py-8 max-w-xl justify-center gap-2">
            <form className="animate-in flex-1 flex flex-col w-full justify-center gap-2 text-foreground">
                <h2>求職者の登録情報</h2>
                {isFirst ? (
                    <>
                        <DefaultInput name={"lastname"} value={lastname} setter={setLastname} isRequired={true}
                                      labelText={"姓*"} placeholderText={"山田"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"firstname"} value={firstname} setter={setFirstname} isRequired={true}
                                      labelText={"名*"} placeholderText={"太郎"}
                                      inputType={InputType.text}/>
                        <DefaultSelect name={"gender"} value={gender}
                                       setter={setGender} isRequired={true} labelText={"性別*"}
                                       selectData={[]} databaseType={DatabaseType.mst_gender}/>
                        <DefaultInput name={"birthday"} value={birthday} setter={setBirthday} isRequired={true}
                                      labelText={"誕生日*"} placeholderText={""}
                                      inputType={InputType.date}/>
                        <DefaultInput name={"email"} value={email} setter={setEmail} isRequired={true}
                                      labelText={"メールアドレス*"} placeholderText={"email@example.com"}
                                      inputType={InputType.email}/>

                        <DefaultSelect name={"nationalityId"} value={nationality_id}
                                       setter={setNationality_id} isRequired={true} labelText={"国籍*"}
                                       selectData={nationalityData} databaseType={DatabaseType.mst_nationality}/>

                        <DefaultInput name={"desired_annual_income"}
                                      value={desired_annual_income}
                                      setter={setDesired_annual_income} isRequired={true}
                                      labelText={"希望年収*"} placeholderText={"希望年収*"}
                                      inputType={InputType.number}/>

                        <DefaultInput name={"desired_change_job_date"}
                                      value={desired_change_job_date}
                                      setter={setDesired_change_job_date} isRequired={true}
                                      labelText={"転職希望日*"} placeholderText={""}
                                      inputType={InputType.date}/>

                        <DefaultInput name={"residence_qualification_expired"}
                                      value={residence_qualification_expired}
                                      setter={setResidence_qualification_expired} isRequired={true}
                                      labelText={"在留資格期限*"} placeholderText={""}
                                      inputType={InputType.date}/>

                        <DefaultSelect name={"desired_work_location"}
                                       value={desired_work_location}
                                       setter={setDesired_work_location} isRequired={true} labelText={"希望勤務地*"}
                                       selectData={workLocationData} databaseType={DatabaseType.mst_work_location}/>
                    </>
                ) : (
                    <>
                        <DefaultInput name={"lastname"} value={lastname} setter={setLastname} isRequired={true}
                                      labelText={"姓*"} placeholderText={"山田"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"firstname"} value={firstname} setter={setFirstname} isRequired={true}
                                      labelText={"名*"} placeholderText={"太郎"}
                                      inputType={InputType.text}/>
                        <DefaultInput name={"middlename"} value={middlename != null ? middlename : undefined}
                                      setter={setMiddlename} isRequired={false}
                                      labelText={"ミドルネーム"} placeholderText={"ミドルネーム"}
                                      inputType={InputType.text}/>
                        <DefaultSelect name={"gender"} value={gender}
                                       setter={setGender} isRequired={true} labelText={"性別*"}
                                       selectData={[]} databaseType={DatabaseType.mst_gender}/>
                        <DefaultInput name={"birthday"} value={birthday} setter={setBirthday} isRequired={true}
                                      labelText={"誕生日*"} placeholderText={""}
                                      inputType={InputType.date}/>
                        <DefaultInput name={"zipcode"} value={zipcode != null ? zipcode : undefined}
                                      setter={setZipcode} isRequired={false}
                                      labelText={"郵便番号"} placeholderText={"0000000 ※ハイフンなしでご入力ください"}
                                      inputType={InputType.text}/>

                        <DefaultInput name={"address1"} value={address1 != null ? address1 : undefined}
                                      setter={setAddress1} isRequired={false}
                                      labelText={"住所1"} placeholderText={"東京都港区浜松町2丁目2番15号"}
                                      inputType={InputType.text}/>

                        <DefaultInput name={"address2"} value={address2 != null ? address2 : undefined}
                                      setter={setAddress2} isRequired={false}
                                      labelText={"住所2"} placeholderText={"浜松町ダイヤビル2F"}
                                      inputType={InputType.text}/>

                        <DefaultInput name={"phone"} value={phone != null ? phone : undefined}
                                      setter={setPhone} isRequired={false}
                                      labelText={"電話番号"}
                                      placeholderText={"00000000000 ※ハイフンなしでご入力ください"}
                                      inputType={InputType.text}/>

                        <DefaultInput name={"email"} value={email} setter={setEmail} isRequired={true}
                                      labelText={"メールアドレス*"} placeholderText={"email@example.com"}
                                      inputType={InputType.email}/>

                        <DefaultSelect name={"nationalityId"} value={nationality_id}
                                       setter={setNationality_id} isRequired={true} labelText={"国籍*"}
                                       selectData={nationalityData} databaseType={DatabaseType.mst_nationality}/>

                        <DefaultInput name={"current_annual_income"}
                                      value={current_annual_income != null ? current_annual_income : undefined}
                                      setter={setCurrent_annual_income} isRequired={false}
                                      labelText={"現在の年収"} placeholderText={"現在の年収"}
                                      inputType={InputType.number}/>

                        <DefaultInput name={"desired_annual_income"}
                                      value={desired_annual_income}
                                      setter={setDesired_annual_income} isRequired={true}
                                      labelText={"希望年収*"} placeholderText={"希望年収*"}
                                      inputType={InputType.number}/>

                        <DefaultSelect name={"spouse"} value={spouse ? spouse : ""}
                                       setter={setSpouse} isRequired={false} labelText={"配偶者"}
                                       selectData={[]} databaseType={DatabaseType.mst_spouse}/>

                        <DefaultSelect name={"desired_occupation_id_1"}
                                       value={desired_occupation_id_1 ? desired_occupation_id_1 : ""}
                                       setter={setDesired_occupation_id_1} isRequired={false} labelText={"希望職種1"}
                                       selectData={occupationData} databaseType={DatabaseType.mst_occupation}/>

                        <DefaultSelect name={"desired_occupation_id_2"}
                                       value={desired_occupation_id_2 ? desired_occupation_id_2 : ""}
                                       setter={setDesired_occupation_id_2} isRequired={false} labelText={"希望職種2"}
                                       selectData={occupationData} databaseType={DatabaseType.mst_occupation}/>

                        <DefaultSelect name={"desired_occupation_id_3"}
                                       value={desired_occupation_id_3 ? desired_occupation_id_3 : ""}
                                       setter={setDesired_occupation_id_3} isRequired={false} labelText={"希望職種3"}
                                       selectData={occupationData} databaseType={DatabaseType.mst_occupation}/>

                        <DefaultInput name={"desired_change_job_date"}
                                      value={desired_change_job_date}
                                      setter={setDesired_change_job_date} isRequired={true}
                                      labelText={"転職希望日*"} placeholderText={""}
                                      inputType={InputType.date}/>

                        <DefaultSelect name={"residence_qualification_id"}
                                       value={residence_qualification_id ? residence_qualification_id : ""}
                                       setter={setResidence_qualification_id} isRequired={false} labelText={"在留資格"}
                                       selectData={residenceQualificationData}
                                       databaseType={DatabaseType.mst_residence_qualification}/>

                        <DefaultInput name={"residence_qualification_expired"}
                                      value={residence_qualification_expired}
                                      setter={setResidence_qualification_expired} isRequired={true}
                                      labelText={"在留資格期限*"} placeholderText={""}
                                      inputType={InputType.date}/>

                        <DefaultSelect name={"desired_work_location"}
                                       value={desired_work_location}
                                       setter={setDesired_work_location} isRequired={true} labelText={"希望勤務地*"}
                                       selectData={workLocationData} databaseType={DatabaseType.mst_work_location}/>

                        {/*<FileUploadInput name={"residence_qualification_front_image_url"}*/}
                        {/*                 value={residence_qualification_front_image_url != null ? residence_qualification_front_image_url : undefined}*/}
                        {/*                 isRequired={false}*/}
                        {/*                 labelText={"在留資格カード表のファイルURL"}*/}
                        {/*                 placeholderText={"在留資格カード表のファイルURL"}*/}
                        {/*                 inputType={InputType.text} isImage={true} fileSetter={setResidence_qualification_front_image_url}/>*/}

                        {/*<FileUploadInput name={"residence_qualification_back_image_url"}*/}
                        {/*                 value={residence_qualification_back_image_url != null ? residence_qualification_back_image_url : undefined}*/}
                        {/*                 isRequired={false}*/}
                        {/*                 labelText={"在留資格カード裏のファイルURL"}*/}
                        {/*                 placeholderText={"在留資格カード裏のファイルURL"}*/}
                        {/*                 inputType={InputType.text} isImage={true} fileSetter={setResidence_qualification_back_image_url}/>*/}

                        {/*<FileUploadInput name={"profile_image_url"}*/}
                        {/*                 value={profile_image_url != null ? profile_image_url : undefined}*/}
                        {/*                 isRequired={false}*/}
                        {/*                 labelText={"プロフィール画像のファイルURL"}*/}
                        {/*                 placeholderText={"プロフィール画像のファイルURL"}*/}
                        {/*                 inputType={InputType.text} isImage={true} fileSetter={setProfile_image_url}/>*/}

                        <FileUploadInput name={"resume_file_url"}
                                         value={resume_file_url != null ? resume_file_url : undefined}
                                         isRequired={false}
                                         labelText={"履歴書のファイルURL"}
                                         placeholderText={"履歴書のファイルURL"}
                                         inputType={InputType.text} isImage={false} fileSetter={setResume_file_url}/>

                        <DefaultInput name={"resume_file_name"}
                                      value={resume_file_name != null ? resume_file_name : undefined}
                                      setter={setResume_file_name} isRequired={false}
                                      labelText={"履歴書のファイル名"}
                                      placeholderText={"履歴書のファイル名"}
                                      inputType={InputType.text}/>
                    </>
                )}

                <SubmitButton
                    formAction={onSubmit}
                    className="bg-green-500 hover:bg-green-600 rounded-md px-4 py-2 text-white mb-2"
                    pendingText="ユーザ情報更新中..."
                >
                    保存
                </SubmitButton>
                {message !== "" && (
                    <p className="mt-4 p-4 bg-foreground/10 text-foreground text-center">
                        {message}
                    </p>
                )}
            </form>
        </div>
    );
}

実装の確認

前回作成したメッセージ画面まで、企業側でログインして進めます。

するとデフォルトの`面接前`という表記が追加されていることがわかります。
こちらをクリックすると、ダイアログが表示されます。

別の項目を選択してOKを押すと確認ダイアログが表示されます。

OKを押すとステータスの更新が確認できます。

求職者側でも変更が確認できました。

その他参考資料など

弊社では『マッチングワン』という『低コスト・短期にマッチングサービスを構築できる』サービスを展開しており、今回ご紹介するコードは、その『マッチングワン』でも使われているコードとなります。
本記事で紹介したようなプロダクトを開発されたい場合は、是非お問い合わせください。

またTodoONada株式会社では、この記事で紹介した以外にも、Supabase・Next.jsを使ったアプリの作成方法についてご紹介しています。
下記記事にて一覧を見ることができますので、ぜひこちらもご覧ください!

お問合せ&各種リンク

presented by

サポートしていただくと、筆者のやる気がガンガンアップします!