Merge branch 'dev-frontend-top' into backend-tobuy-stable

backend-tobuy-adddish
Masaharu.Kato 5 months ago
commit ad810e58cd
  1. 26
      frontend/src/App.tsx
  2. 28
      frontend/src/components/CategoryDropDown.tsx
  3. 32
      frontend/src/components/Layout.tsx
  4. 274
      frontend/src/pages/StockPage.tsx
  5. 292
      frontend/src/pages/TaskListPage.tsx
  6. 191
      frontend/src/services/api.ts
  7. 45
      frontend/src/types/types.ts
  8. 24
      package-lock.json

@ -9,6 +9,7 @@ import Layout from './components/Layout';
import LoginPage from './pages/LoginPage';
import RegisterPage from './pages/RegisterPage';
import TaskListPage from './pages/TaskListPage';
import StockPage from './pages/StockPage';
import './App.css';
/**
@ -92,6 +93,31 @@ const App: React.FC = () => {
</PrivateRoute>
}
/>
</Route>
<Route path="/" element={<Layout />}>
{/* ルートパスへのアクセスはタスク一覧にリダイレクト */}
<Route index element={<Navigate to="/stock" replace />} />
{/* タスク一覧は認証が必要なため、PrivateRouteでラップ */}
<Route
path="stock"
element={
<PrivateRoute>
<StockPage />
</PrivateRoute>
}
/>
{/* テストページへのルートを追加 */}
<Route
path="stock"
element={
<PrivateRoute>
<StockPage />
</PrivateRoute>
}
/>
</Route>
</Routes>
</BrowserRouter>

@ -0,0 +1,28 @@
import React, { useState } from "react";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
import FormControl from "@mui/material/FormControl";
import InputLabel from "@mui/material/InputLabel";
const CategoryDropDown = () => {
const [selectedValue, setSelectedValue] = useState(""); // 選択された値の状態管理
return (
<FormControl sx={{ width: "50%", marginBottom:2 }}>
<InputLabel id="demo-simple-select-label"></InputLabel>
<Select
labelId="demo-simple-select-label"
value={selectedValue}
onChange={(event) => setSelectedValue(event.target.value)}
>
<MenuItem value="乳製品"></MenuItem>
<MenuItem value="肉・魚"></MenuItem>
<MenuItem value="野菜"></MenuItem>
<MenuItem value="調味料">調</MenuItem>
<MenuItem value="その他"></MenuItem>
</Select>
</FormControl>
);
};
export default CategoryDropDown;

@ -3,13 +3,13 @@
* AppBar
*/
import React, { useState } from 'react';
import {
AppBar,
Toolbar,
Typography,
Container,
Box,
Button,
import {
AppBar,
Toolbar,
Typography,
Container,
Box,
Button,
Drawer,
List,
ListItemText,
@ -18,9 +18,11 @@ import {
Divider,
IconButton
} from '@mui/material';
import {
import {
Menu as MenuIcon,
ListAlt as ListAltIcon,
Inventory as InventoryIcon, // テストページ用のアイコン
Science as ScienceIcon, // 鈴木
} from '@mui/icons-material';
import { useNavigate, Outlet, useLocation } from 'react-router-dom';
@ -91,13 +93,23 @@ const Layout: React.FC = () => {
role="presentation"
>
<List>
<ListItemButton
onClick={() => handleNavigate('/tasks')}
<ListItemButton
onClick={() => handleNavigate('/tasks')}
selected={isSelected('/tasks')}
>
<ListItemIcon><ListAltIcon /></ListItemIcon>
<ListItemText primary="タスク一覧" />
</ListItemButton>
{/* テストページへのリンクを追加 */}
{/* 在庫リストへのリンクを追加 */}
<ListItemButton
onClick={() => handleNavigate('/stock')}
selected={isSelected('/stock')}
>
<ListItemIcon><InventoryIcon /></ListItemIcon>
<ListItemText primary="在庫管理" />
</ListItemButton>
<Divider />
</List>
</Box>

@ -0,0 +1,274 @@
/**
*
*
*/
import React, { useState, useEffect } from 'react';
import { stockApi } from '../services/api';
import { Stock } from '../types/types';
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from "@mui/material";
import {
Container,
Typography,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Button,
Box,
} from '@mui/material';
import { TASK_ERRORS } from '../constants/errorMessages';
import CategoryDropDown from "../components/CategoryDropDown";
const StockPage: React.FC = () => {
const [stocks, setStocks] = useState<Stock[]>([]);
// 在庫編集ダイアログの表示状態
const [openDialog, setOpenDialog] = useState(false);
// コンポーネントマウント時にタスク一覧を取得
useEffect(() => {
fetchStocks();
}, []);
/**
* APIからタスク一覧を取得する関数
* state(tasks)
*/
const fetchStocks = async () => {
try {
const stocks = await stockApi.getStocks();
setStocks(stocks.stock_array);
} catch (error) {
console.error(`${TASK_ERRORS.FETCH_FAILED}:`, error);
}
};
/**
* (ISO 8601)yyyy/MM/ddに変換する関数
*/
const formatDate = (isoString: string): string => {
const date = new Date(isoString);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0"); // 月は0始まりなので+1
const day = date.getDate().toString().padStart(2, "0");
return `${year}/${month}/${day}`;
};
/* Dateyyyy/MM/dd
const formatDate = (date: Date): string => {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0"); // 月は0始まりなので+1
const day = date.getDate().toString().padStart(2, "0");
return `${year}/${month}/${day}`;
};
*/
return (
<Container>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* タスク編集ボタン */}
<Button color="success"
type="submit"
variant="contained"
sx={{ mt: 3, mb: 2, left: '80%' }}
>
</Button>
{/* タスク削除ボタン */}
<Button color="error"
type="submit"
variant="contained"
sx={{ mt: 3, mb: 2, left: '82%' }}
>
</Button>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* 乳製品一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{/* 要素が無かったら表示しない */}
{stocks.filter(stock => stock.category === "乳製品").length === 0 ? null : (
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{stocks
.filter(stock => stock.category == "乳製品") // 乳製品だけ抽出
.map(stock => (
<TableRow key={stock.stock_id}
>
<TableCell>{stock.stuff_name}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.exp_date)}</TableCell>
<TableCell>{formatDate(stock.buy_date)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* 肉・魚一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{stocks.filter(stock => stock.category == "肉" || stock.category == "魚").length === 0 ? null : (
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{stocks
.filter(stock => stock.category == "肉" || stock.category == "魚") // 肉と魚だけ抽出
.map(stock => (
<TableRow key={stock.stock_id}>
<TableCell>{stock.stuff_name}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.exp_date)}</TableCell>
<TableCell>{formatDate(stock.buy_date)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* 野菜一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{stocks.filter(stock => stock.category === "野菜").length === 0 ? null : (
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{stocks
.filter(stock => stock.category == "野菜") // 野菜だけ抽出
.map(stock => (
<TableRow key={stock.stock_id}>
<TableCell>{stock.stuff_name}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.exp_date)}</TableCell>
<TableCell>{formatDate(stock.buy_date)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
<Typography variant="h4" component="h1" gutterBottom>
調
</Typography>
{/* 調味料一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{stocks.filter(stock => stock.category === "調味料").length === 0 ? null : (
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{stocks
.filter(stock => stock.category == "調味料") // 調味料だけ抽出
.map(stock => (
<TableRow key={stock.stock_id}>
<TableCell>{stock.stuff_name}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.exp_date)}</TableCell>
<TableCell>{formatDate(stock.buy_date)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
<Typography variant="h4" component="h1" gutterBottom>
</Typography>
{/* その他一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px', marginBottom: "20px" }}>
{stocks.filter(stock => stock.category === "その他").length === 0 ? null : (
<TableContainer component={Paper}>
<Table>
<TableHead sx={{ backgroundColor: "#dcdcdc", color: "#333" }}>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{stocks
.filter(stock => stock.category == "その他") // その他だけ抽出
.map(stock => (
<TableRow key={stock.stock_id}>
<TableCell>{stock.stuff_name}</TableCell>
<TableCell>{stock.amount}</TableCell>
<TableCell>{stock.price}</TableCell>
<TableCell>{formatDate(stock.exp_date)}</TableCell>
<TableCell>{formatDate(stock.buy_date)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</div>
</Container>
);
};
export default StockPage;

@ -3,10 +3,11 @@
*
*/
import React, { useState, useEffect } from 'react';
import { taskApi } from '../services/api';
import { toBuyApi, stuffApi } from '../services/api';
import {
Container,
Typography,
Tooltip,
List,
ListItem,
ListItemText,
@ -21,21 +22,51 @@ import {
TextField,
Button,
Box,
FormControlLabel,
FormGroup,
FormControl,
InputLabel,
Select,
MenuItem
} from '@mui/material';
import { Add as AddIcon, Delete as DeleteIcon } from '@mui/icons-material';
import { Task } from '../types/types';
import {
Add as AddIcon, Delete as DeleteIcon, ShoppingBasket as ShoppingBasketIcon,
SoupKitchen as SoupKitchenIcon
} from '@mui/icons-material';
import { Task, ToBuy, Stuff } from '../types/types';
import { TASK_ERRORS } from '../constants/errorMessages';
//import { FaCarrot } from "react-icons/fa6"; //エラー起きる いったん保留
import CategoryDropDown from "../components/CategoryDropDown";
// 新規タスクの初期状態
const EMPTY_TASK = { title: '', description: '', completed: false };
const EMPTY_TASK: Omit<ToBuy, 'tobuy_id' | 'stuff_id'> & { stuff_id: number | null, category: string } & { newAddition: boolean } = {
stuff_id: null,
stuff_name: '',
amount: 0,
shop: '',
category: '',
newAddition: false,
}
const TaskListPage: React.FC = () => {
// タスク一覧の状態管理
const [tasks, setTasks] = useState<Task[]>([]);
const [tobuys, setToBuys] = useState<ToBuy[]>([]);
// 新規タスク作成ダイアログの表示状態
const [openDialog, setOpenDialog] = useState(false);
// 新規タスクの入力内容
const [newTask, setNewTask] = useState(EMPTY_TASK);
//在庫登録ダイアログの表示状態
const [openInfoDialog, setOpenInfoDialog] = useState(false);
const [selectedTask, setSelectedTask] = useState<ToBuy>();
const [newToBuy, setNewToBuy] = useState(EMPTY_TASK);
const [stuffs, setStuffs] = useState<Stuff[]>([]);
// コンポーネントマウント時にタスク一覧を取得
useEffect(() => {
@ -48,36 +79,42 @@ const TaskListPage: React.FC = () => {
*/
const fetchTasks = async () => {
try {
const tasks = await taskApi.getTasks();
setTasks(tasks);
const tobuys = await toBuyApi.getToBuys();
setToBuys(tobuys.tobuy_array);
} 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;
const onChangeCategory = async (category: string) => {
setNewToBuy({ ...newToBuy, category })
const result = await stuffApi.getStuffs(category)
setStuffs(result.stuff_array)
}
await taskApi.updateTask(taskId, { ...task, completed: !task.completed });
fetchTasks(); // 更新後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.UPDATE_FAILED}:`, error);
}
};
// /**
// * タスクの完了状態を切り替えるハンドラー
// * 対象タスクの完了状態を反転させてAPIに更新を要求
// */
// const handleToggleComplete = async (taskId: number) => {
// try {
// const task = tasks.find(t => t.id === taskId);
// if (!task) return;
// await toBuyApi.updateTask(taskId, { ...task, completed: !task.completed });
// fetchTasks(); // 更新後のタスク一覧を再取得
// } catch (error) {
// console.error(`${TASK_ERRORS.UPDATE_FAILED}:`, error);
// }
// };
/**
*
* IDのタスクをAPIを通じて削除
*/
const handleDeleteTask = async (taskId: number) => {
const handleDeleteTask = async (toBuyId: number) => {
try {
await taskApi.deleteTask(taskId);
await toBuyApi.deleteToBuy(toBuyId);
fetchTasks(); // 削除後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.DELETE_FAILED}:`, error);
@ -91,9 +128,9 @@ const TaskListPage: React.FC = () => {
*/
const handleCreateTask = async () => {
try {
await taskApi.createTask(newTask);
await toBuyApi.addToBuy(newToBuy);
setOpenDialog(false); // ダイアログを閉じる
setNewTask(EMPTY_TASK); // 入力内容をリセット
setNewToBuy(EMPTY_TASK); // 入力内容をリセット
fetchTasks(); // 作成後のタスク一覧を再取得
} catch (error) {
console.error(`${TASK_ERRORS.CREATE_FAILED}:`, error);
@ -106,12 +143,12 @@ const TaskListPage: React.FC = () => {
</Typography>
{/* タスク一覧表示エリア - 青い背景のコンテナ */}
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px'}}>
<div style={{ border: '3px solid black', borderRadius: '8px', backgroundColor: '#add8e6', height: 'auto', padding: '20px' }}>
<List>
{/* タスク一覧をマップして各タスクをリストアイテムとして表示 */}
{tasks.map((task) => (
{tobuys.map((tobuy) => (
<ListItem
key={task.id}
key={tobuy.tobuy_id}
sx={{
bgcolor: 'background.paper',
mb: 1,
@ -120,75 +157,210 @@ const TaskListPage: React.FC = () => {
}}
>
{/* タスク完了状態を切り替えるチェックボックス */}
{/*}
<Checkbox
checked={task.completed}
onChange={() => handleToggleComplete(task.id)}
/>
*/}
{/* タスクのタイトルと説明 - 完了状態に応じて取り消し線を表示 */}
<ListItemText
primary={task.title}
secondary={task.description}
primary={tobuy.stuff_name}
secondary={tobuy.amount}
sx={{
textDecoration: task.completed ? 'line-through' : 'none',
textDecoration: false ? 'line-through' : 'none',
}}
/>
{/* タスク削除ボタン */}
{/* 買い物リスト:食材情報記入ボタン */}
<ListItemSecondaryAction>
<IconButton
edge="end"
aria-label="delete"
onClick={() => handleDeleteTask(task.id)}
<Tooltip title="食材情報追加">
<IconButton
edge="end"
aria-label="食材情報追加"
onClick={() => setOpenInfoDialog(true)}
//onClick={() => handleDeleteTask(task.id)}
>
<ShoppingBasketIcon />
</IconButton>
</Tooltip>
{/* 買い物リスト:食材削除ボタン */}
<Tooltip title="項目を削除"
componentsProps={{
tooltip: {
sx: {
backgroundColor: "white",
color: "red",
fontSize: "0.8rem",
padding: "6px",
borderRadius: "6px",
},
},
}}
>
<DeleteIcon />
</IconButton>
<IconButton
edge="end"
aria-label="delete"
onClick={() => handleDeleteTask(tobuy.tobuy_id)}
>
<DeleteIcon />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
</div>
{/* 新規タスク作成ボタン - 画面下部に固定表示 */}
<Fab
color="primary"
sx={{ position: 'fixed', bottom: 16, left: '50%', transform: 'translateX(-50%)'}}
onClick={() => setOpenDialog(true)}
>
<AddIcon />
</Fab>
{/* 新規材料作成ボタン - 画面下部に固定表示 */}
<Tooltip title="材料のみ追加">
<Fab
color="primary"
sx={{ position: 'fixed', bottom: 16, left: '40%', transform: 'translateX(-50%)' }}
onClick={() => setOpenDialog(true)}
>
<AddIcon />
</Fab>
</Tooltip>
{/*新規料理追加ボタン - 画面下部に固定表示 */}
<Tooltip title="料理から追加">
<Fab
color="primary"
sx={{ position: 'fixed', bottom: 16, left: '60%', transform: 'translateX(-50%)' }}
onClick={() => setOpenDialog(true)}
>
<SoupKitchenIcon />
</Fab>
</Tooltip>
{/* 新規タスク作成ダイアログ */}
<Dialog open={openDialog} onClose={() => setOpenDialog(false)} disableScrollLock={true}>
<DialogTitle></DialogTitle>
<Box display="flex" alignItems="center">
<DialogTitle sx={{ flexGrow: 1 }}></DialogTitle>
<FormGroup row>
<FormControlLabel
control={<Checkbox />}
label="食材を新規追加"
checked={newToBuy.newAddition}
onChange={(e) => setNewToBuy({ ...newToBuy, newAddition: (e.target as HTMLInputElement).checked })}
/>
</FormGroup>
</Box>
<DialogContent>
<Box sx={{ pt: 1 }}>
{/*材料カテゴリ選択 */}
<FormControl sx={{ width: "50%", marginBottom: 2 }}>
<InputLabel id="demo-simple-select-label"></InputLabel>
<Select
labelId="demo-simple-select-label"
value={newToBuy.category}
onChange={(e) => onChangeCategory(e.target.value) }
>
<MenuItem value="乳製品"></MenuItem>
<MenuItem value="肉・魚"></MenuItem>
<MenuItem value="野菜"></MenuItem>
<MenuItem value="調味料">調</MenuItem>
<MenuItem value="その他"></MenuItem>
</Select>
</FormControl>
{!newToBuy.newAddition && <FormControl sx={{ width: "100%", marginBottom: 2 }}>
<InputLabel id="demo-simple-select-label"></InputLabel>
<Select
labelId="demo-simple-select-label"
value={newToBuy.stuff_id}
onChange={(e) => setNewToBuy({ ...newToBuy, stuff_id: Number(e.target.value) })}
>
{stuffs.map((stuff) => (
<MenuItem key={stuff.stuff_id} value={stuff.stuff_id}>
{stuff.stuff_name}
</MenuItem>
))}
</Select>
</FormControl>}
{/* タスクタイトル入力フィールド */}
{newToBuy.newAddition && <TextField
autoFocus
margin="dense"
label="材料名"
fullWidth
value={newToBuy.stuff_name}
onChange={(e) => setNewToBuy({ ...newToBuy, stuff_name: e.target.value })}
sx={{ marginBottom: 2 }}
/>}
{/* 数量入力フィールド */}
<TextField
margin="dense"
label="数量"
fullWidth
value={newToBuy.amount}
onChange={(e) => {
const value = e.target.value;
const parsedValue = parseInt(value, 10); // 数値に変換
if (!isNaN(parsedValue)) {
setNewToBuy({ ...newToBuy, 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} variant="contained">
</Button>
</DialogActions>
</Dialog>
{/*在庫登録のための数値入力ダイアログ */}
<Dialog open={openInfoDialog} onClose={() => setOpenInfoDialog(false)} disableScrollLock={true}>
<DialogTitle></DialogTitle>
<DialogContent>
<Box sx={{ pt: 1 }}>
{/* 価格入力フィールド */}
<TextField
autoFocus
margin="dense"
label="タイトル"
label="価格"
fullWidth
/>
{/* 消費・賞味期限入力フィールド */}
<TextField
margin="dense"
label="消費・賞味期限(yyyy/MM/dd)"
fullWidth
value={newTask.title}
onChange={(e) => setNewTask({ ...newTask, title: e.target.value })}
multiline
/>
{/* タスク説明入力フィールド - 複数行入力可能 */}
{/* 購入日入力フィールド */}
<TextField
margin="dense"
label="説明"
label="購入日(yyyy/MM/dd)"
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 onClick={() => setOpenInfoDialog(false)}></Button>
<Button onClick={() => {
if (selectedTask) {
handleDeleteTask(selectedTask.tobuy_id)
setOpenInfoDialog(false)
}
}}
variant="contained">
</Button>
</DialogActions>
</Dialog>
</Container>
);
};

@ -3,7 +3,7 @@
* APIとの通信を担当するモジュール
*
*/
import { LoginCredentials, RegisterCredentials, AuthResponse, Task } from '../types/types';
import { LoginCredentials, RegisterCredentials, AuthResponse, Task, ToBuy, Stuff, Stock } from '../types/types';
import { AUTH_ERRORS, TASK_ERRORS } from '../constants/errorMessages';
// APIのベースURL - 環境変数から取得するか、デフォルト値を使用
@ -82,7 +82,188 @@ export const authApi = {
},
};
/**
* API機能を提供するオブジェクト
*
*/
export const toBuyApi = {
/**
*
* @returns
*/
getToBuys: async (): Promise<{ "tobuy_array": ToBuy[] }> => {
// const response = await fetch(`${API_BASE_URL}/api/tobuy/get`, {
// headers: getHeaders(), // 認証トークンを含むヘッダー
// });
// if (!response.ok) {
// throw new Error(TASK_ERRORS.FETCH_FAILED);
// }
// return response.json();
return {
"tobuy_array": [
{
"tobuy_id": 1,
"stuff_id": 2,
"stuff_name": "じゃがいも",
"amount": 3,
"shop": "shopXXX"
},
{
"tobuy_id": 2,
"stuff_id": 5,
"stuff_name": "にんじん",
"amount": 1
}
]
}
},
/**
*
* @param tobuy
* @returns
*/
addToBuy: async (tobuy: Omit<ToBuy, 'stuff_id' | 'tobuy_id'> & { stuff_id: number | null, category: string }): Promise<any> => {
// const response = await fetch(`${API_BASE_URL}/api/tasks`, {
// method: 'POST',
// headers: getHeaders(),
// body: JSON.stringify(tobuy),
// });
// if (!response.ok) {
// throw new Error(TASK_ERRORS.CREATE_FAILED);
// }
// return response.json();
return {
"result": true,
"tobuy_id": 1,
"stuff_id": 6,
"message": "追加に成功しました",
}
},
/**
*
* @param id ID
*/
deleteToBuy: async (tobuy_id: number): Promise<{ result: boolean }> => {
// const response = await fetch(`${API_BASE_URL}/api/tobuy/delete`, {
// method: 'DELETE',
// headers: getHeaders(),
// body: JSON.stringify({tobuy_id}),
// });
// if (!response.ok) {
// throw new Error(TASK_ERRORS.DELETE_FAILED);
// }
return {
"result": true
}
},
}
export const stuffApi = {
getStuffs: async (category: string): Promise<{ stuff_array: Stuff[] }> => {
const data = [
{ "stuff_id": 1, "stuff_name": "牛乳", "category": "乳製品" },
{ "stuff_id": 2, "stuff_name": "ヨーグルト", "category": "乳製品" },
{ "stuff_id": 3, "stuff_name": "チーズ", "category": "乳製品" },
{ "stuff_id": 4, "stuff_name": "バター", "category": "乳製品" },
{ "stuff_id": 5, "stuff_name": "生クリーム", "category": "乳製品" },
{ "stuff_id": 6, "stuff_name": "鮭", "category": "魚・肉" },
{ "stuff_id": 7, "stuff_name": "鶏むね肉", "category": "魚・肉" },
{ "stuff_id": 8, "stuff_name": "豚バラ肉", "category": "魚・肉" },
{ "stuff_id": 9, "stuff_name": "牛ひき肉", "category": "魚・肉" },
{ "stuff_id": 10, "stuff_name": "まぐろ", "category": "魚・肉" },
{ "stuff_id": 11, "stuff_name": "にんじん", "category": "野菜" },
{ "stuff_id": 12, "stuff_name": "キャベツ", "category": "野菜" },
{ "stuff_id": 13, "stuff_name": "ほうれん草", "category": "野菜" },
{ "stuff_id": 14, "stuff_name": "玉ねぎ", "category": "野菜" },
{ "stuff_id": 15, "stuff_name": "ピーマン", "category": "野菜" },
{ "stuff_id": 16, "stuff_name": "醤油", "category": "調味料" },
{ "stuff_id": 17, "stuff_name": "味噌", "category": "調味料" },
{ "stuff_id": 18, "stuff_name": "塩", "category": "調味料" },
{ "stuff_id": 19, "stuff_name": "砂糖", "category": "調味料" },
{ "stuff_id": 20, "stuff_name": "酢", "category": "調味料" },
{ "stuff_id": 21, "stuff_name": "米", "category": "その他" },
{ "stuff_id": 22, "stuff_name": "パスタ", "category": "その他" },
{ "stuff_id": 23, "stuff_name": "小麦粉", "category": "その他" },
{ "stuff_id": 24, "stuff_name": "卵", "category": "その他" },
{ "stuff_id": 25, "stuff_name": "豆腐", "category": "その他" }
]
const filtered = data.filter(stuff => stuff.category == category)
return {
"stuff_array": filtered
}
}
}
export const stockApi = {
/**
*
* @returns
*/
getStocks: async (): Promise<{ "stock_array": Stock[] }> => {
// const response = await fetch(`${API_BASE_URL}/api/tobuy/get`, {
// headers: getHeaders(), // 認証トークンを含むヘッダー
// });
// if (!response.ok) {
// throw new Error(TASK_ERRORS.FETCH_FAILED);
// }
// return response.json();
return {
"stock_array": [
{
"stock_id": 1,
"stuff_id": 10,
"stuff_name": "豚肉",
"amount": 100,
"price": 200,
"buy_date": "2025-05-18T09:00:00.000Z",
"last_update": "2025-05-18T09:00:00.000Z",
"exp_date": "2025-05-19T10:15:00.000Z",
"category": "肉"
},
{
"stock_id": 2,
"stuff_id": 1,
"stuff_name": "トマト",
"amount": 10,
"price": 200,
"buy_date": "2025-05-18T09:00:00.000Z",
"last_update": "2025-05-18T09:00:00.000Z",
"exp_date": "2025-05-19T10:15:00.000Z",
"category": "野菜"
}
]
}
},
}
/**
* ()
* API機能を提供するオブジェクト
*
*/
@ -121,12 +302,12 @@ export const taskApi = {
},
/**
*
* @param task IDID
*
* @param task ,
* @returns
*/
createTask: async (task: Omit<Task, 'id' | 'userId' | 'createdAt' | 'updatedAt'>): Promise<Task> => {
const response = await fetch(`${API_BASE_URL}/api/tasks`, {
addStuff: async (task: Omit<Task, 'userId' | 'createdAt' | 'price' | 'buyDate' | 'expirationDate' | 'newAddition'>): Promise<Task> => {
const response = await fetch(`${API_BASE_URL}/api/tubuy/add`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify(task),

@ -4,12 +4,51 @@
*/
export interface Task {
id: number; // タスクの一意識別子
title: string; // タスクのタイトル
description?: string; // タスクの詳細説明(任意)
stuff_name: string; // タスクのタイトル
amount: number; //材料の数量
price: number; //材料の値段
buyDate:Date; //購入日時
expirationDate: Date; //賞味・消費期限
//description?: string; // タスクの詳細説明(任意)
completed: boolean; // タスクの完了状態
userId: number; // タスクの所有者ID
createdAt: string; // タスク作成日時
updatedAt: string; // タスク更新日時
newAddition: boolean //材料を新規追加するかどうか
//updatedAt: string; // タスク更新日時
}
/**
*
*
*/
export interface ToBuy {
tobuy_id: number,
stuff_id: number,
stuff_name: string,
amount: number,
shop?: string,
}
export interface Stuff {
stuff_id: number,
stuff_name: string,
category: string,
}
/**
*
*
*/
export interface Stock {
stock_id: number,
stuff_id: number,
stuff_name: string,
amount: number,
price: number,
buy_date: string,
last_update: string,
exp_date: string,
category: string,
}
/**

24
package-lock.json generated

@ -0,0 +1,24 @@
{
"name": "joint_exc",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
"peerDependencies": {
"react": "*"
}
}
}
}
Loading…
Cancel
Save