Merge remote-tracking branch 'origin/dev-frontend-add_dishes' into dev-frontend-在庫登録

dev-frontend-在庫登録
akito.nishiwaki 5 months ago
commit ebdbaa45d3
  1. 20
      frontend/src/App.tsx
  2. 7
      frontend/src/components/Layout.tsx
  3. 82
      frontend/src/pages/AddDishes1.tsx
  4. 278
      frontend/src/pages/AddDishes2.tsx
  5. 12
      frontend/src/pages/TestPage.tsx

@ -11,6 +11,9 @@ import RegisterPage from './pages/RegisterPage';
import TaskListPage from './pages/TaskListPage';
import StockPage from './pages/StockPage';
import './App.css';
// 必要なインポートを追加
import AddDishes1 from './pages/AddDishes1';
import AddDishes2 from './pages/AddDishes2';
/**
* Material UIテーマを定義
@ -94,6 +97,23 @@ const App: React.FC = () => {
}
/>
{/* テストページへのルートを追加 */}
<Route
path="add1"
element={
<PrivateRoute>
<AddDishes1 />
</PrivateRoute>
}
/>
<Route
path="add2"
element={
<PrivateRoute>
<AddDishes2 />
</PrivateRoute>
}
/>
</Route>
<Route path="/" element={<Layout />}>
{/* ルートパスへのアクセスはタスク一覧にリダイレクト */}

@ -103,6 +103,13 @@ const Layout: React.FC = () => {
{/* テストページへのリンクを追加 */}
{/* 在庫リストへのリンクを追加 */}
<ListItemButton
onClick={() => handleNavigate('/add1')}
selected={isSelected('/add1')}
>
<ListItemIcon><ScienceIcon /></ListItemIcon>
<ListItemText primary="料理の追加" />
</ListItemButton>
<ListItemButton
onClick={() => handleNavigate('/stock')}
selected={isSelected('/stock')}

@ -0,0 +1,82 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
AppBar,
Toolbar,
Typography,
Container,
Box,
Button,
Drawer,
List,
ListItemText,
ListItemIcon,
ListItemButton,
Divider,
IconButton,
TextField,
Paper,
Alert,
Link,
Grid,
} from '@mui/material';
import { LoginCredentials } from '../types/types';
import { authApi } from '../services/api';
import { GENERAL_ERRORS } from '../constants/errorMessages';
const AddDishes1: React.FC = () => {
const navigate = useNavigate();
const [dish, setDish] = useState("");
// エラーメッセージの状態管理
const [error, setError] = useState(false);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setDish(event.target.value);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); // フォームのデフォルト送信動作を防止
if (!dish.trim()) {
alert("エラー");
setError(true);
} else {
alert("送信成功!");
localStorage.setItem("dishName", dish); // ローカルストレージにフォームに入力された料理名を保存
navigate('/add2', { state: dish }); // 料理名から材料名追加ページにリダイレクト
}
};
return (
<div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
<Box component="form" onSubmit={handleSubmit}>
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '10px', textAlign: 'center'}}>
<TextField
required
// id="username"
label="追加・編集したい料理名を入力"
variant="outlined"
InputLabelProps={{ style: { fontSize: "40px" }}}
style={{width: "80%" }}
InputProps={{ style: { fontSize: "40px"} }}
name="dish_name"
// autoComplete="username"
autoFocus
value={dish}
onChange={handleChange}
error={error}
helperText={error ? "入力が必要です" : ""}
/>
</div>
<div style={{position: "fixed", left: "75%", transform: 'translateX(-50%)', bottom: "10px"}}>
<Button type="submit" variant='contained' sx={{ width: "250px", height: "60px", fontSize: "40px" }} color="primary" >
</Button>
</div>
</Box>
</div>
);
};
export default AddDishes1;

@ -0,0 +1,278 @@
/**
*
*
*/
import React, { useState, useEffect } from 'react';
import { taskApi } from '../services/api';
import {
Container,
Typography,
Tooltip,
List,
ListItem,
ListItemText,
ListItemSecondaryAction,
IconButton,
Checkbox,
Fab,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Button,
Box,
MenuItem,
Select,
FormControl,
InputLabel
} from '@mui/material';
import {
Add as AddIcon, Delete as DeleteIcon, ShoppingBasket as ShoppingBasketIcon,
SoupKitchen as SoupKitchenIcon
} from '@mui/icons-material';
import { Task } from '../types/types';
import { TASK_ERRORS } from '../constants/errorMessages';
import { GENERAL_ERRORS } from '../constants/errorMessages';
import CategoryDropDown from "../components/CategoryDropDown";
// 新規タスクの初期状態(画面表示用)
const EMPTY_TASK = { id: 0, title: '', amount: 0, completed: false };
// 新規タスクの初期状態(データベース登録用)
const EMPTY_TASK_DATA_BASE = { id: 0, title: '', amount: 0, completed: false };
interface Empty_Task {
title: string; // 食材名
amount: number; // 食材の個数
completed: boolean; //
}
const AddDishes2: React.FC = () => {
const receivedData = localStorage.getItem("dishName");
// タスク一覧の状態管理
const [tasks, setTasks] = useState<Task[]>([]);
const [addtasks, setAddTasks] = useState<Empty_Task[]>([]);
// エラーメッセージの状態管理
const [error, setError] = useState(false);
// 新規タスク作成ダイアログの表示状態
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);
}
};
/**
*
* IDのタスクをAPIを通じて削除
*/
const handleDeleteTask = async (index: number) => {
try {
let newAddTasks = [...addtasks]; // 配列をコピー
newAddTasks.splice(index, 1);
setAddTasks(newAddTasks);
// fetchTasks(); // 削除後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.DELETE_FAILED}:`, error);
}
};
/**
*
* APIに送信して新規作成
*
*/
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); // フォームのデフォルト送信動作を防止
if (addtasks[0] == null) {
setError(true);
alert("食材を追加してください");
} else {
alert("送信成功!");
handleCreateTask_DataBase();
// localStorage.setItem("dishName", dish); // ローカルストレージにフォームに入力された料理名を保存
// navigate('/add2', { state: dish }); // 料理名から材料名追加ページにリダイレクト
}
};
const handleCreateTask_Temp = async () => {
try {
// await taskApi.createTask(newTask);
let newAddTasks = [...addtasks]; // 配列をコピー
newAddTasks.push(newTask);
setAddTasks(newAddTasks);
setOpenDialog(false); // ダイアログを閉じる
setNewTask(EMPTY_TASK); // 入力内容をリセット
// fetchTasks(); // 作成後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.CREATE_FAILED}:`, error);
}
};
const handleCreateTask_DataBase = async () => {
try {
for (let i = 0; i < addtasks.length; i++) {
await taskApi.createTask(addtasks[i]);
}
setOpenDialog(false); // ダイアログを閉じる
setNewTask(EMPTY_TASK); // 入力内容をリセット
// fetchTasks(); // 作成後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.CREATE_FAILED}:`, error);
}
};
return (
<Box>
<div>
<h1></h1>
<p style={{fontSize: "40px"}}>{receivedData}</p>
</div>
<List>
{/* タスク一覧をマップして各タスクをリストアイテムとして表示 */}
{addtasks.map((task, index) => (
<ListItem
key={index}
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',
}}
/>
{/* 食材の個数を表示 */}
<ListItemText
primary={<Typography style={{textAlign:"center"}}><br />
{task.amount}
</Typography>
}
// secondary={task.description}
primaryTypographyProps={{ align: "right", marginRight: "20%", fontSize: "20px" }}
/>
{/* 買い物リスト:食材情報記入ボタン */}
<ListItemSecondaryAction>
<Tooltip title="食材情報追加">
<IconButton
edge="end"
aria-label="食材情報追加"
//onClick={() => handleDeleteTask(task.id)}
>
<ShoppingBasketIcon />
</IconButton>
</Tooltip>
{/* 買い物リスト:食材削除ボタン */}
<Tooltip title="項目を削除"
componentsProps={{
tooltip: {
sx: {
backgroundColor: "white",
color: "red",
fontSize: "0.8rem",
padding: "6px",
borderRadius: "6px",
},
},
}}
>
<IconButton
edge="end"
aria-label="delete"
onClick={() => handleDeleteTask(index)}
>
<DeleteIcon />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
<div style={{position: "fixed", left: "75%", transform: 'translateX(-50%)', bottom: "20%"}}>
<Button variant='contained' sx={{ width: "250px", height: "60px", fontSize: "40px" }}
color="primary" onClick={() => setOpenDialog(true)}>
</Button>
</div>
<div style={{position: "fixed", width: "60%", left: "50%", transform: 'translateX(-50%)', bottom: "2%"}}>
<Button variant='contained' sx={{ width: "100%", height: "100px", fontSize: "40px" }}
color="primary" onClick={handleSubmit}>
</Button>
</div>
{/* 新規タスク作成ダイアログ */}
<Dialog open={openDialog} onClose={() => setOpenDialog(false)} disableScrollLock={true}>
<DialogTitle></DialogTitle>
<DialogContent>
<Box sx={{ pt: 1 }}>
{/*材料カテゴリ選択 */}
<CategoryDropDown></CategoryDropDown>
{/* タスクタイトル入力フィールド */}
<TextField
autoFocus
margin="dense"
label="材料名"
fullWidth
value={newTask.title}
onChange={(e) => setNewTask({ ...newTask, title: e.target.value })}
sx={{ marginBottom: 2 }}
/>
{/* 数量入力フィールド */}
<TextField
margin="dense"
label="数量"
fullWidth
value={newTask.amount}
onChange={(e) => {
const value = e.target.value;
const parsedValue = parseInt(value, 10); // 数値に変換
if (!isNaN(parsedValue)) {
setNewTask({ ...newTask, amount: parsedValue }); // number型で保存
}
}}
sx={{ width: "20%" }}
type="number"
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }} // ここで整数のみ許可
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenDialog(false)}></Button>
<Button onClick={handleCreateTask_Temp} variant="contained">
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default AddDishes2;

@ -0,0 +1,12 @@
import React from 'react';
import { Box } from '@mui/material';
const TestPage: React.FC = () => {
return (
<Box>
{/* 白紙のページ - 何も表示しない */}
</Box>
);
};
export default TestPage;
Loading…
Cancel
Save