Reactの勉強②　入力画面

**今回の狙い**
フロント処理となるREACT（MUI）を利用して、入力画面機能を準備します。
前回の記事の環境を、そのまま使って、Reactのプログラムだけ差し替えます。

https://qiita.com/MasahiroShouya/items/64bca802cea7d55ac559

**Reactで作成する入力画面のイメージ**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3878922/02ee3fbb-bb9e-420f-81b4-c04b9b9af0ad.png)

狙いとしては、チェック処理やメッセージ等は、後からユーザ要望が出てくるので、簡単に修正できるように、別ファイルにしたいと思います。

**RedmineのRestAPI**
RedmineのAPIを利用して、Redmineのユーザを登録したいと思います。

https://www.redmine.org/projects/redmine/wiki/Rest_Users

APIの仕様としては、POSTアクセスで、ヘッダー情報にAPIキーをセットして、登録するユーザ情報をJSONを渡す事になります。

**Reactのプログラム**
以下のプログラムを用意します。
```diff_conf:フォルダ構成
└── src
    ├── index.js　　　（初期起動プログラム）
    ├── App.js　　　　（画面へ表示される本体）
    ├── ErrCheck.js　（エラーチェック処理部）
    └── msg.jp.js　　（エラーメッセージ部）
```
```diff_javascript:index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
```
```diff_javascript:App.js
import { useForm } from 'react-hook-form';
import React from 'react';
import { TextField, Button } from '@mui/material';
import Grid from '@mui/material/Grid';
import ErrCheck from './ErrCheck';
import { yupResolver } from '@hookform/resolvers/yup';

export default function App() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    defaultValues: {
      loginid: '1234567890',
      passwd: '12345Aa!',
      firstnm: 'a',
      lastnm: 'b',
      mailad: 'c@d'
    },
    resolver: yupResolver(ErrCheck),
  });

  const onsubmit = async (data) => {
    try {
      const response = await fetch('http://localhost:3001/users.json', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Redmine-API-Key': '※ここにredmineのAPIキー',
        },
        body: JSON.stringify({
          user: {
            login: data.loginid,
            password: data.passwd,
            firstname: data.firstnm,
            lastname: data.lastnm,
            mail: data.mailad,
          },
        }),
      });

      if (response.ok) {
        const responseData = await response.json();
        console.log('Redmine登録成功:', responseData);
        alert('ユーザー登録が完了しました。');
      } else {
        const errorData = await response.json();
        console.error('Redmine登録失敗:', errorData);
        alert('ユーザー登録に失敗しました。');
      }
    } catch (error) {
      console.error('送信エラー:', error);
      alert('送信中にエラーが発生しました。');
    }
  };

  const onerror = err => console.log(err);
  return (
    <div className="App">
    <form onSubmit={handleSubmit(onsubmit, onerror)} noValidate>
    <Grid container spacing={1}>
      <Grid size={{xs:12}}>
        <TextField id="loginid" type="text" label={ErrCheck.fields.loginid.describe().label}
                    error={!!errors.loginid}
                    helperText={errors.loginid?.message}
            {...register('loginid')} />
      </Grid>
      <Grid size={{xs:12}}>
        <TextField id="passwd" type="password" label={ErrCheck.fields.passwd.describe().label}
                    error={!!errors.passwd}
                    helperText={errors.passwd?.message}
            {...register('passwd')} />
      </Grid>
      <Grid size={{xs:12}}>
        <TextField id="firstnm" type="text" label={ErrCheck.fields.firstnm.describe().label}
                    error={!!errors.firstnm}
                    helperText={errors.firstnm?.message}
            {...register('firstnm')} />
      </Grid>
      <Grid size={{xs:12}}>
        <TextField id="lastnm" type="text" label={ErrCheck.fields.lastnm.describe().label}
                    error={!!errors.lastnm}
                    helperText={errors.lastnm?.message}
            {...register('lastnm')} />
      </Grid>
      <Grid size={{xs:12}}>
        <TextField id="mailad" type="text" label={ErrCheck.fields.mailad.describe().label}
                    error={!!errors.mailad}
                    helperText={errors.mailad?.message}
            {...register('mailad')} />
      </Grid>
      <Grid size={{xs:12}}>
        <Button variant="contained" type="submit">登録</Button>
      </Grid>
    </Grid>
    </form>
    </div>
  );
}
```
```diff_javascript:ErrCheck.js
import msg from './msg.jp';

const hasMixedCase = /(?=.*[a-z])(?=.*[A-Z])/;
const hasNumber = /(?=.*[0-9])/;
const hasSymbol = /(?=.*[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?])/;

const ErrCheck = msg.object({
  loginid  : msg.string().label('ログインＩＤ')
         .required()
         .max(10),
  passwd  : msg.string().label('パスワード')
         .min(8)
         .required()
         .matches(hasMixedCase, 'パスワードは英大文字と英小文字を含めてください。')
         .matches(hasNumber, 'パスワードは数字を含めてください。')
         .matches(hasSymbol, 'パスワードは記号を含めてください。'),
  firstnm  : msg.string().label('姓')
         .required(),
  lastnm  : msg.string().label('名')
         .required(),
  mailad  : msg.string().label('メールアドレス')
         .required()
         .email(),
});
export default ErrCheck;
```
```diff_javascript:msg.jp.js
import * as yup from 'yup';

const jpLocale = {
  mixed: {
    required: param => `${param.label}は必須です。`,
  },
  string: {
    min: param => `${param.label}は${param.min}文字以上でなければなりません。`,
    max: param => `${param.label}は${param.max}文字以下でなければなりません。`,
    matches: param => `${param.label}は「${param.regex}」形式に一致していなければなりません。`,
    email: param => `${param.label}はメールアドレス形式でなければなりません。`,
  },
};
yup.setLocale(jpLocale);
export default yup;
```

**入力エラーの場合**
赤文字＋赤枠になり、下段にエラーメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3878922/47b8d3b5-c7ba-444a-87ae-b3ff72114eae.png)
ちなみに、RedmineのAPIにも、エラーチェックが実装されています。
　１）同じ「ログインID」は登録できない
　２）同じ「メールアドレス」は登録できない
 エラーの場合は、以下のポップアップ画面が表示されるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3878922/864b517c-3bdd-4612-b876-aec88c2bd1f8.png)
正常に登録されると、以下のポップアップ画面が表示されるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3878922/ce3f25e8-b45c-40d7-b98b-fb41a9f717a4.png)
登録したユーザで、RedmineにログインできればOKです。

お疲れ様でしたぁ～。