##まず私について
初めまして!
kenshoと申します。
2019年からプログラミングの勉強を本格的に始めて、web制作フリーランスを経験、その後はIT企業のインターン生として働きながら独学でwebエンジニアの勉強をしてきました。
2021年4月から新卒で上場企業のwebエンジニアとして働く予定です。
よかったらTwitterもやっておりますので気軽にフォローお願いします♪
Twitterアカウント↓
健将@WEBエンジニア×明大生
##前回までのあらすじ
前回は、このPDCAアプリのログイン、登録、ホームのフロント部分を完成させた。
↓↓
DjangoとReactでPDCAアプリを作る その1
##今回やること
git⬇️
https://github.com/kenshow-blog/workapplication
今回は、このアプリのPDCAのフロント部分をReactとTypeScriptで完成させる。
###アプリ構造
フロントエンド
.
├── README.md
├── package-lock.json
├── package.json
├── public
├── src
│ ├── App.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── app
│ │ └── store.ts
│ ├── features
│ │ ├── auth #認証部分
│ │ │ ├── Auth.module.css
│ │ │ ├── Auth.tsx
│ │ │ └── authSlice.ts
│ │ ├── core #共通画面(ヘッダーなど)
│ │ │ ├── Core.module.css
│ │ │ └── Core.tsx
│ │ ├── home #ホーム画面
│ │ │ ├── Home.module.css
│ │ │ └── Home.tsx
│ │ ├── pdca #PDCA部分
│ │ │ ├── DeleteDialog.tsx
│ │ │ ├── Pdca.module.css
│ │ │ ├── Pdca.tsx
│ │ │ ├── PdcaDetail.tsx
│ │ │ └── pdcaSlice.ts
│ │ └── types.ts
│ ├── index.css
│ ├── index.tsx
│ ├── logo.svg
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ └── setupTests.ts
└── tsconfig.json
###pdcaSliceを作成する
今回コードがかなり長くなってしまいました😅
その代わり全て詳細にコメントしておきましたのでご了承ください👍
import { createSlice, PayloadAction, createAsyncThunk } from "@reduxjs/toolkit";
import { RootState } from "../../app/store";
import axios from "axios";
import { ACTION, PDCA_STATE, CATEGORY, PDC } from "../types";
//バックからACTIONデータをGETしてる
export const fetchAsyncGetActions = createAsyncThunk(
"get/actions", async () => {
const res = await axios.get<ACTION[]>(
`${process.env.REACT_APP_API_URL}/pdca/action/`,
{
headers: {
Authorization: `JWT ${localStorage.localJWT}`,
},
}
);
return res.data;
}
);
//バックからカテゴリーデータをGETしている
export const fetchAsyncGetCategory = createAsyncThunk(
"get/category", async () => {
const res = await axios.get<CATEGORY[]>(
`${process.env.REACT_APP_API_URL}/pdca/category/`,
{
headers: {
Authorization: `JWT ${localStorage.localJWT}`,
},
}
);
return res.data;
}
);
//バックからpdcデータをGETしている
export const fetchAsyncGetPdc = createAsyncThunk(
"get/pdc", async () => {
const res = await axios.get<PDC[]>(
`${process.env.REACT_APP_API_URL}/pdca/pdc/`,
{
headers: {
Authorization: `JWT ${localStorage.localJWT}`,
},
}
);
return res.data;
}
);
//バックのpdcへPOSTして、新しくpdcを作成している
export const fetchAsyncCreatePdc = createAsyncThunk(
"post/pdc", async (pdc: PDC) => {
const res = await axios.post<PDC>(
`${process.env.REACT_APP_API_URL}/pdca/pdc/`,
pdc,
{
headers: {
Authorization: `JWT ${localStorage.localJWT}`,
},
}
);
return res.data;
}
);
//バックのactionへPOSTして、新しくa(アクション)を作成している
export const fetchAsyncCreateAction = createAsyncThunk(
"post/action", async (action: ACTION) => {
const res = await axios.post<ACTION>(
`${process.env.REACT_APP_API_URL}/pdca/action/`,
action,
{
headers: {
Authorization: `JWT ${localStorage.localJWT}`,
},
}
);
return res.data;
}
);
//バックのpdcへputメソッドで更新を命令している。
//EDIT機能の際に使用
export const fetchAsyncUpdatePdc = createAsyncThunk(
"update/pdc", async (pdc: PDC) => {
const res = await axios.put<PDC>(
`${process.env.REACT_APP_API_URL}/pdca/pdc/${pdc.id}/`,
pdc,
{
headers: {
Authorization: `JWT ${localStorage.localJWT}`,
},
}
);
return res.data;
}
);
//バックのactionへputメソッドで更新を命令している。
//EDIT機能の際に使用
export const fetchAsyncUpdateAction = createAsyncThunk(
"update/action", async (action: ACTION) => {
const res = await axios.put<ACTION>(
`${process.env.REACT_APP_API_URL}/pdca/action/${action.id}/`,
action,
{
headers: {
Authorization: `JWT ${localStorage.localJWT}`,
},
}
);
return res.data;
}
);
//バックのpdcへ削除を命令
//a(アクション)は「on_delete=models.CASCADE」としていて、
//外部キーとして結ばれているpdcがdeleteされると一緒にdeleteされるようにしてある
export const fetchAsyncDeletePdca = createAsyncThunk(
"delete/pdca",
async (id: number) => {
const res = await axios.delete(
`${process.env.REACT_APP_API_URL}/pdca/pdc/${id}/`,
{
headers: {
"Content-Type": "application/json",
Authorization: `JWT ${localStorage.localJWT}`,
},
}
);
return id;
}
)
//それぞれの初期値を設定
export const initialState: PDCA_STATE = {
actions: [
{
id: 0,
action: "",
pdca: 0,
category_item: "",
action_user: 0,
category: 0,
created_at: "",
updated_at: "",
},
],
category:[
{
id: 0,
item: ""
},
],
pdc: [
{
id: 0,
userPdc: 0,
title: "",
plan: "",
do: "",
check: "",
created_at: "",
updated_at: "",
},
],
selectedPdc: {
id: 0,
userPdc: 0,
title: "",
plan: "",
do: "",
check: "",
created_at: "",
updated_at: "",
},
editedPdc: {
id: 0,
userPdc: 0,
title: "",
plan: "",
do: "",
check: "",
created_at: "",
updated_at: "",
},
editedAction:
{
id: 0,
action: "",
pdca: 0,
category_item: "未選択",
action_user: 0,
category: 5,
created_at: "",
updated_at: "",
},
editView: false,
createView: false,
}
export const pdcaSlice = createSlice({
name: "pdca",
initialState,
reducers: {
selectPdca(state, action: PayloadAction<PDC>) {
state.selectedPdc = action.payload
},
editPdc(state, action: PayloadAction<PDC>) {
state.editedPdc = action.payload
},
editAction(state, action: PayloadAction<ACTION>) {
state.editedAction = action.payload
},
setEditView(state) {
state.editView = true;
},
resetEditView(state) {
state.editView = false;
},
setCreateView(state) {
state.createView = true;
},
resetCreateView(state) {
state.createView = false;
}
},
extraReducers: (builder) => {
//HOME画面でactionsのリストを表示させる際に使用
builder.addCase(
fetchAsyncGetActions.fulfilled,
(state, action: PayloadAction<ACTION[]>) => {
return {
...state,
actions: action.payload,
}
}
);
builder.addCase(fetchAsyncGetActions.rejected, () => {
window.location.href = "/";
});
builder.addCase(
fetchAsyncGetCategory.fulfilled,
(state, action : PayloadAction<CATEGORY[]>) => {
return {
...state,
category: action.payload,
}
}
);
builder.addCase(
fetchAsyncGetPdc.fulfilled,
(state, action : PayloadAction<PDC[]>) => {
return {
...state,
pdc: action.payload,
}
}
);
builder.addCase(fetchAsyncGetPdc.rejected, () => {
window.location.href = "/";
});
//更新前のpdc stateをmapで展開してその中で更新したデータのidと同じものを抽出して
//更新後のデータに上書きさせるような処理をしている。action.payloadに更新後のデータが格納れている。
builder.addCase(
fetchAsyncUpdatePdc.fulfilled,
(state, action : PayloadAction<PDC>) => {
return {
...state,
pdc: state.pdc.map((t) =>
t.id === action.payload.id ? action.payload : t)
,
editedPdc: initialState.editedPdc,
editView: initialState.editView
}
}
);
builder.addCase(fetchAsyncUpdatePdc.rejected, () => {
window.location.href = "/";
});
//更新前のactions stateをmapで展開してその中で更新したデータのidと同じものを抽出して、
//更新後のデータに上書きさせるような処理をしている。action.payloadに更新後のデータが格納れている。
builder.addCase(
fetchAsyncUpdateAction.fulfilled,
(state, action : PayloadAction<ACTION>) => {
return {
...state,
actions: state.actions.map((t) =>
t.id === action.payload.id ? action.payload : t)
,
editedAction: initialState.editedAction
}
}
);
builder.addCase(fetchAsyncUpdateAction.rejected, () => {
window.location.href = "/";
});
//pdcをcreateした後、現在のpdc state(配列)の先頭に新しく作成した、pdcを入れたいので、
//配列の先頭にaction.payloadを入れている。
//pdcを作成した後、actionも作成する。しかし、actionとpdcの紐付けがまだされていないので、
//actionが作成される直前に、pdca情報をaction.payload.idで書き換えて、紐付けさせてる。
builder.addCase(
fetchAsyncCreatePdc.fulfilled,
(state, action: PayloadAction<PDC>) => {
return {
...state,
pdc: [action.payload, ...state.pdc],
editedAction: {...state.editedAction, pdca: action.payload.id},
editedPdc: initialState.editedPdc
}
}
)
builder.addCase(fetchAsyncCreatePdc.rejected, () => {
window.location.href = "/";
});
builder.addCase(
fetchAsyncCreateAction.fulfilled,
(state, action: PayloadAction<ACTION>) => {
return {
...state,
actions: [action.payload, ...state.actions],
editedAction: initialState.editedAction,
}
}
)
builder.addCase(fetchAsyncCreateAction.rejected, () => {
window.location.href = "/";
});
//filterを利用して、削除したデータのidをskipしている。
//こうすることで現在のpdc stateから削除したpdcを弾くことができる
//他のstateを初期化させておく
builder.addCase(
fetchAsyncDeletePdca.fulfilled,
(state, action: PayloadAction<number>) => {
return {
...state,
pdc: state.pdc.filter((t) => t.id !== action.payload),
editedAction: initialState.editedAction,
editedPdc: initialState.editedPdc,
selectedPdc: initialState.selectedPdc
}
}
);
builder.addCase(fetchAsyncDeletePdca.rejected, () => {
window.location.href = "/";
});
}
});
//下記の実装にて、それぞれのstateをリアルタイムで参照することができる
export const { selectPdca, setEditView, resetEditView, editPdc, editAction, setCreateView, resetCreateView } = pdcaSlice.actions;
export const selectActions = (state: RootState) => state.pdca.actions;
export const selectCategory = (state: RootState) => state.pdca.category;
export const selectPdc = (state: RootState) => state.pdca.pdc;
export const selectSelectedPdc = (state: RootState) => state.pdca.selectedPdc;
export const selectEditPdc = (state: RootState) => state.pdca.editView;
export const selectCreatePdca = (state: RootState) => state.pdca.createView;
export const selectEditedPdc = (state: RootState) => state.pdca.editedPdc;
export const selectEditedAction = (state: RootState) => state.pdca.editedAction;
export default pdcaSlice.reducer;
###storeにpdcaSliceを登録する
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import authReducer from '../features/auth/authSlice';
import pdcaReducer from '../features/pdca/pdcaSlice';
export const store = configureStore({
reducer: {
auth: authReducer,
//今回追記した部分↓↓
pdca: pdcaReducer,
},
});
・
・
・
###1.PDCA一覧のコンポーネント、新規作成コンポーネントを作成する
ここも長い、、笑
コンポーネント部分だけ、引用いたしましたので、全部確認したい場合は、gitを参照してください👍
const Pdca: React.FC = () => {
const classes = useStyles();
const dispatch: AppDispatch = useDispatch();
//pdcのstateをリアルタイムで確認する
const pdc = useSelector(selectPdc);
//新規作成画面に切り替えるか否かを確認している
const createView = useSelector(selectCreatePdca);//true or false
//新規作成画面で入力された値を確認する
const editedPdc = useSelector(selectEditedPdc);
const editedAction = useSelector(selectEditedAction);
const category = useSelector(selectCategory);
//入力された情報を、editPdc stateに入れる
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let value: string = e.target.value;
const name = e.target.name;
dispatch(editPdc({ ...editedPdc, [name]: value}));
}
//入力された情報を、editedAction stateに入れる
const handleInputActionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let value: string | number = e.target.value;
const name = e.target.name;
let test = dispatch(editAction({ ...editedAction, [name]: value}));
}
//選択されたカテゴリー情報を、editedAction stateに入れる
const handleSelectCatChange = (
e: React.ChangeEvent<{ value: unknown }>
) => {
const value = e.target.value as number;
dispatch(editAction({ ...editedAction, category: value }))
}
const create = async () => {
//先にpdcを作成して、返り値をresultに格納する
const result = await dispatch(fetchAsyncCreatePdc(editedPdc));
if (fetchAsyncCreatePdc.fulfilled.match(result)) {
await dispatch(fetchAsyncCreateAction({
id: 0,
action: editedAction.action,
pdca: result.payload.id,//返り値のidをここに入れることでpdcとactionの紐付けを行っている
category_item: editedAction.category_item,
action_user: 0,
category: editedAction.category,
created_at: "",
updated_at: "",
}))
}
//新規作成画面の表示をfalseにして、pdca一覧画面に切り替える
await dispatch(resetCreateView);
window.location.reload();
}
//遷移した際に、実装される部分(useEffect)
useEffect(() => {
const fetchBootLoader = async () => {
await dispatch(fetchAsyncGetCategory());
await dispatch(fetchAsyncGetPdc());
};
fetchBootLoader();
}, [dispatch])
let datas = []
//カテゴリーのselectを作成
let catOptions = category.map((cat) => (
<MenuItem key={cat.id} value={cat.id}>
{cat.item}
</MenuItem>
))
return (
<div className={styles.pdca_body}>
{/*新規作成画面にするのか、pdca一覧画面にするのかをcreateView(true or false)を参照して切り替えてる */}
{createView?
/*新規作成コンポーネントを表示*/
<>
<Link to="/pdca/" style={{ textDecoration: 'none' }}>
<Button className={classes.newPdca} variant="contained" color="primary" onClick={() => dispatch(resetCreateView())}>
PDCA
</Button>
</Link>
<Button
variant="contained"
color="secondary"
size="medium"
onClick={create}
className={classes.newPdca}
>
Create
</Button>
<br />
<TextField
variant="outlined"
label="Title"
type="text"
name="title"
InputProps={{ inputProps: { min: 0, max: 400 }}}
InputLabelProps={{
shrink: true,
}}
value={editedPdc.title}
onChange={handleInputChange}
className={classes.title}
/>
<Grid container>
<Grid item xs={12} sm={3}>
<TextField
label="Plan"
type="text"
name="plan"
variant="outlined"
multiline
InputProps={{ inputProps: { min: 0, max: 400,className: classes.pdc }}}
InputLabelProps={{
shrink: true,
}}
value={editedPdc.plan}
onChange={handleInputChange}
className={styles.pdca}
/>
</Grid>
<Grid item xs={12} sm={3}>
<TextField
variant="outlined"
label="Do"
type="text"
name="do"
multiline
InputProps={{ inputProps: { min: 0, max: 400,className: classes.pdc }}}
InputLabelProps={{
shrink: true,
}}
value={editedPdc.do}
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={12} sm={3} >
<TextField
variant="outlined"
label="Check"
type="text"
name="check"
multiline
InputProps={{ inputProps: { min: 0, max: 400,className: classes.pdc }}}
InputLabelProps={{
shrink: true,
}}
value={editedPdc.check}
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={12} sm={3}>
<TextField
variant="outlined"
label="Action"
type="text"
name="action"
multiline
InputProps={{ inputProps: { min: 0, max: 400,className: classes.aPdc }}}
InputLabelProps={{
shrink: true,
}}
value={editedAction.action}
onChange={handleInputActionChange}
/>
<FormControl className={classes.field}>
<InputLabel>Category</InputLabel>
<Select
name="category"
onChange={handleSelectCatChange}
value={editedAction.category}
>
{catOptions}
</Select>
</FormControl>
</Grid>
</Grid>
</>
:
/*pdca一覧コンポーネントを表示*/
<>
<Link to="/" style={{ textDecoration: 'none' }}>
<Button className={classes.rootHome} variant="contained" onClick={() => dispatch(resetCreateView())}>
HOME
</Button>
</Link>
{/*新規作成コンポーネントに切り替える際に、editPdc, editActionを初期化しておく */}
<Button className={classes.newPdca} variant="contained" color="primary" onClick={ async () =>{
await dispatch(setCreateView())
await dispatch(editPdc(initialState.editedPdc))
await dispatch(editAction(initialState.editedAction))
}}>
New PDCA
</Button>
{/*pdca一覧。クリックするとそれぞれの詳細ページに遷移するようにしてある。その際、詳細ページへ、表示させるpdcaのstateを添付している*/}
<List component="nav" className={classes.root} aria-label="mailbox folders">
{pdc
.slice(0)
.reverse()
.map((pdc) =>(
<ListItem
className={classes.listItem}
key={pdc.id}
>
<Link to={{
pathname: `/pdca/detail/`,
state: {id: pdc.id,
userPdc: pdc.userPdc,
title: pdc.title,
plan: pdc.plan,
do: pdc.do,
check: pdc.check,
created_at: pdc.created_at,
updated_at: pdc.updated_at,}
}}
className={styles.ListItem}
>
<h3 style={{margin: "0", fontSize: "1em"}}>{pdc.created_at}</h3>
<h3 style={{margin: "0", fontSize: "1em"}}>{pdc.title}</h3>
</Link>
</ListItem>
))}
</List>
</>
}
</div>
)
}
export default Pdca
ここで、地味につまづいたのが、TextFieldのカスタマイズ部分。
material-uiのTextFieldの入力部分のcssスタイルを変更したい場合、InputProps={{ inputProps: { min: 0, max: 400,className: classes.pdc }}}
のようにして、InputPropの中にcssをカスタマイズするコードを記述する。
コンポーネントの切り替えには、boolean型のstate(今回で言うとこの、createView)を作成しておくと何かと便利👍
###詳細ページ(閲覧、編集)のコンポーネントを作成する
const PdcaDetail: React.FC<Props> = (props) => {
const classes = useStyles();
//pdca一覧ページから添付されてきた、pdc stateを受け取ってpdc_propsに格納している
let pdc_props = props.location.state;
const dispatch: AppDispatch = useDispatch();
//pdc state(全部)をpdcに格納している
const pdc = useSelector(selectSelectedPdc);
//action state(全部)をpdcに格納している
const actions = useSelector(selectActions);
const category = useSelector(selectCategory);
const editView = useSelector(selectEditPdc);
const editedPdc = useSelector(selectEditedPdc);
const editedAction = useSelector(selectEditedAction);
const [commDlg, setCommDlg] = React.useState(false);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let value: string = e.target.value;
const name = e.target.name;
dispatch(editPdc({ ...editedPdc, [name]: value}));
}
const handleInputActionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let value: string | number = e.target.value;
const name = e.target.name;
dispatch(editAction({ ...editedAction, [name]: value}));
}
const handleSelectCatChange = (
e: React.ChangeEvent<{ value: unknown }>
) => {
const value = e.target.value as number;
dispatch(editAction({ ...editedAction, category: value }))
}
const update = async () => {
//editedAction等が、編集されたであろうstateであるのでそれを引数に入れてstateの更新処理をしている
await dispatch(fetchAsyncUpdateAction(editedAction));
await dispatch(selectPdca(editedPdc));
await dispatch(fetchAsyncUpdatePdc(editedPdc));
}
useEffect(() => {
const fetchBootLoader = async () => {
await dispatch(selectPdca(pdc_props));
await dispatch(fetchAsyncGetActions());
await dispatch(fetchAsyncGetCategory());
};
fetchBootLoader();
}, [dispatch]);
//そのpdcaデータのアクション情報をpdc_props.idを参照してactionContentに格納している
const actionContent = actions.filter((act) => {
return act.pdca === pdc_props.id
})
//カテゴリーのselectを作成している
let catOptions = category.map((cat) => (
<MenuItem key={cat.id} value={cat.id}>
{cat.item}
</MenuItem>
))
return (
<div className={styles.pdca_body}>
<Link to="/" style={{ textDecoration: 'none' }}>
<Button className={classes.rootHome} variant="contained" onClick={() => dispatch(resetEditView())}>
Home
</Button>
</Link>
<Link to="/pdca/" style={{ textDecoration: 'none' }}>
<Button className={classes.editPdca} variant="contained" color="primary" onClick={() => dispatch(resetEditView())}>
PDCA
</Button>
</Link>
{/*editView(true or false)で詳細ページの編集画面と閲覧画面の表示を切り替えている*/}
{editView? <>
{/*編集画面*/}
<Button
className={classes.editPdca}
variant="contained"
color="secondary"
size="medium"
onClick={update}
>
Update
</Button>
<br />
</> :
<>
<Button variant="contained" className={classes.editPdca} color="secondary" onClick={ async () =>{
await dispatch(editPdc(pdc));
/*そのpdcaデータのa(アクション)がnullで保存されていなければ、その中身を表示する処理を書いている。*/
if(actionContent[0].action !== 'undefined') {
await dispatch(editAction(actionContent[0]));
}
await dispatch(setEditView())
}}>
Edit
</Button>
{/*削除する際に削除確認コンポーネントを表示するようにしている*/}
<Button className={classes.editPdca} color="inherit" variant="contained" style={{marginLeft:"10px"}}
onClick={
() => {
setCommDlg(true)
}} >
DEL
</Button>
</>
}
{/*削除確認コンポーネント*/}
<DeleteDialog
msg={"Are you sure you want to permanently delete this files ?"}
isOpen={commDlg}
doYes={async () => {
await dispatch(fetchAsyncDeletePdca(pdc.id))
await setCommDlg(false)
window.location.href = "/pdca/";
}}
doNo={() => {setCommDlg(false)}}
/>
{editView?
<>
<TextField
variant="outlined"
label="Title"
type="text"
name="title"
InputProps={{ inputProps: { min: 0, max: 400 }}}
InputLabelProps={{
shrink: true,
}}
value={editedPdc.title}
onChange={handleInputChange}
className={classes.title}
/>
<Grid container>
<Grid item xs={3}>
<TextField
label="Plan"
type="text"
name="plan"
variant="outlined"
multiline
InputProps={{ inputProps: { min: 0, max: 400,className: classes.pdc }}}
InputLabelProps={{
shrink: true,
}}
value={editedPdc.plan}
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={3}>
<TextField
label="Do"
type="text"
name="do"
variant="outlined"
multiline
InputProps={{ inputProps: { min: 0, max: 400,className: classes.pdc }}}
InputLabelProps={{
shrink: true,
}}
value={editedPdc.do}
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={3}>
<TextField
label="Check"
type="text"
name="check"
variant="outlined"
multiline
InputProps={{ inputProps: { min: 0, max: 400,className: classes.pdc }}}
InputLabelProps={{
shrink: true,
}}
value={editedPdc.check}
onChange={handleInputChange}
/>
</Grid>
<Grid item xs={3}>
<TextField
label="Action"
type="text"
name="action"
variant="outlined"
multiline
InputProps={{ inputProps: { min: 0, max: 400,className: classes.aPdc }}}
InputLabelProps={{
shrink: true,
}}
{/*そのpdcaデータのa(アクション)が空欄の場合、valueに空欄を入れるようにしている*/}
value={editedAction?.action ?
editedAction.action : ""}
onChange={handleInputActionChange}
/>
<FormControl className={classes.field}>
<InputLabel>Category</InputLabel>
<Select
name="category"
onChange={handleSelectCatChange}
value={editedAction.category}
>
{catOptions}
</Select>
</FormControl>
</Grid>
</Grid> </>:
<>
<h1 className={classes.pdcaTitle}>{pdc.title}</h1>
<Grid container>
<Grid item xs={3}>
<h2>P</h2>
<p className={classes.pdcaSelect}>
{pdc.plan}
</p>
</Grid>
<Grid item xs={3}>
<h2>D</h2>
<p className={classes.pdcaSelect}>
{pdc.do}
</p>
</Grid>
<Grid item xs={3}>
<h2>C</h2>
<p className={classes.pdcaSelect}>
{pdc.check}
</p>
</Grid>
<Grid item xs={3}>
<h2>A</h2>
{actionContent[0]?.action ?
<>
<p className={classes.pdcaSelectA}>
{actionContent[0].action}
</p>
<strong>Category</strong>
<br/>
<p className={classes.category}>
{actionContent[0].category_item}
</p>
</>
: <></>
}
</Grid>
</Grid>
</>
}
</div>
)
}
export default PdcaDetail
###削除確認コンポーネントを作成する
この削除確認画面は、とても汎用的に使うことができるので、ここにて、シェアしておく
参考記事↓
[React + Material-UIで確認ダイアログを作成してみた。]
(https://qiita.com/pepaperon_p/items/4e1b25e1e0ebd54268fa)
import React, {useEffect} from 'react';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
export const DeleteDialog: React.FunctionComponent<
{ msg: any, isOpen: any, doYes: any, doNo: any}
> = ({msg, isOpen, doYes, doNo}) => {
const [open, setOpen] = React.useState(false)
useEffect(() => {
setOpen(isOpen)
}, [isOpen])
return (
<div>
<Dialog
open={open}
keepMounted
onClose={() => doNo()}
aria-labelledby="common-dialog-title"
aria-describedby="common-dialog-description"
>
<DialogContent>
{msg}
</DialogContent>
<DialogActions>
<Button onClick={() => doNo()} color="primary">
No
</Button>
<Button onClick={() => doYes()} color="primary">
Yes
</Button>
</DialogActions>
</Dialog>
</div>
)
}
これで、削除ボタンを押した際、「本当に削除しますか?? yes or no」
を表示して、yesならば、deleteメソッドを呼び出す、noなら削除確認コンポーネントを閉じることができる
これで、今回のアプリのフロント部分が全て完成した🎉🎉
##ここまでの感想
今回は、オリジナルアプリ2号機である.前回作成したTwitterアプリ( DjangoとReact redux TypeScriptを使ってオリジナルアプリを作ってみました(TwitterAPI)その1 )に比べて、外部apiを使用しなかった分サーバーサイドの実装は簡単だったが、フロント部分の実装にかなり世話を焼かされた。
しかし、前回のは逆に、フロント部分の実装がそれほど難しくなかったので、この二つのアプリでバランスよくスキルを磨けたのではないかと思う。
###今後の展望
今後下記の機能をこのアプリに組み込めたらと思う。
1.一つのpdcaにつき、アクションを複数作成できるようにする
2.Google カレンダーAPIを使ってgoogleカレンダーとの紐付けをできるようにする。
3.他の生産性向上させてくれるフレームワークを組み込んで、機能を拡張させる
1が、できる前提で今回のアプリを開発していたのだが、終わってから早速拡張しようとしたところ、実装がとても難しく止まってしまっている状態である。
なんとか実装できるようにしていきたい。
###最後に
ここまで読んでくださりありがとうございました!🙇♂️🙇♂️
また、Twitterでも日々の積み上げや、プログラミング学習についてのツイートをしておりますので、よかったらフォローと応援の程よろしくお願いします!🙇♂️
Twitterアカウント↓
健将@WEBエンジニア×明大生