You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
joint_exc/frontend/src/pages/TaskListPage.tsx

195 lines
6.2 KiB

5 months ago
/**
*
*
*/
import React, { useState, useEffect } from 'react';
import { taskApi } from '../services/api';
import {
Container,
Typography,
List,
ListItem,
ListItemText,
ListItemSecondaryAction,
IconButton,
Checkbox,
Fab,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Button,
Box,
} from '@mui/material';
import { Add as AddIcon, Delete as DeleteIcon } from '@mui/icons-material';
import { Task } from '../types/types';
import { TASK_ERRORS } from '../constants/errorMessages';
// 新規タスクの初期状態
const EMPTY_TASK = { title: '', description: '', completed: false };
const TaskListPage: React.FC = () => {
// タスク一覧の状態管理
const [tasks, setTasks] = useState<Task[]>([]);
// 新規タスク作成ダイアログの表示状態
const [openDialog, setOpenDialog] = useState(false);
// 新規タスクの入力内容
const [newTask, setNewTask] = useState(EMPTY_TASK);
// コンポーネントマウント時にタスク一覧を取得
useEffect(() => {
fetchTasks();
}, []);
/**
* APIからタスク一覧を取得する関数
* state(tasks)
*/
const fetchTasks = async () => {
try {
const tasks = await taskApi.getTasks();
setTasks(tasks);
} catch (error) {
console.error(`${TASK_ERRORS.FETCH_FAILED}:`, error);
}
};
/**
*
* APIに更新を要求
*/
const handleToggleComplete = async (taskId: number) => {
try {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
await taskApi.updateTask(taskId, { ...task, completed: !task.completed });
fetchTasks(); // 更新後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.UPDATE_FAILED}:`, error);
}
};
/**
*
* IDのタスクをAPIを通じて削除
*/
const handleDeleteTask = async (taskId: number) => {
try {
await taskApi.deleteTask(taskId);
fetchTasks(); // 削除後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.DELETE_FAILED}:`, error);
}
};
/**
*
* APIに送信して新規作成
*
*/
const handleCreateTask = async () => {
try {
await taskApi.createTask(newTask);
setOpenDialog(false); // ダイアログを閉じる
setNewTask(EMPTY_TASK); // 入力内容をリセット
fetchTasks(); // 作成後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.CREATE_FAILED}:`, error);
}
};
return (
<Container>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* タスク一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px'}}>
<List>
{/* タスク一覧をマップして各タスクをリストアイテムとして表示 */}
{tasks.map((task) => (
<ListItem
key={task.id}
sx={{
bgcolor: 'background.paper',
mb: 1,
borderRadius: 1,
boxShadow: 1,
}}
>
{/* タスク完了状態を切り替えるチェックボックス */}
<Checkbox
checked={task.completed}
onChange={() => handleToggleComplete(task.id)}
/>
{/* タスクのタイトルと説明 - 完了状態に応じて取り消し線を表示 */}
<ListItemText
primary={task.title}
secondary={task.description}
sx={{
textDecoration: task.completed ? 'line-through' : 'none',
}}
/>
{/* タスク削除ボタン */}
<ListItemSecondaryAction>
<IconButton
edge="end"
aria-label="delete"
onClick={() => handleDeleteTask(task.id)}
>
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
</div>
{/* 新規タスク作成ボタン - 画面下部に固定表示 */}
<Fab
color="primary"
sx={{ position: 'fixed', bottom: 16, left: '50%', transform: 'translateX(-50%)'}}
onClick={() => setOpenDialog(true)}
>
<AddIcon />
</Fab>
{/* 新規タスク作成ダイアログ */}
<Dialog open={openDialog} onClose={() => setOpenDialog(false)} disableScrollLock={true}>
<DialogTitle></DialogTitle>
<DialogContent>
<Box sx={{ pt: 1 }}>
{/* タスクタイトル入力フィールド */}
<TextField
autoFocus
margin="dense"
label="タイトル"
fullWidth
value={newTask.title}
onChange={(e) => setNewTask({ ...newTask, title: e.target.value })}
/>
{/* タスク説明入力フィールド - 複数行入力可能 */}
<TextField
margin="dense"
label="説明"
fullWidth
multiline
rows={4}
value={newTask.description}
onChange={(e) => setNewTask({ ...newTask, description: e.target.value })}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenDialog(false)}></Button>
<Button onClick={handleCreateTask} variant="contained">
</Button>
</DialogActions>
</Dialog>
</Container>
);
};
export default TaskListPage;