Implement car reservation schedule management system
Co-authored-by: pdf114514 <57948770+pdf114514@users.noreply.github.com> Agent-Logs-Url: https://github.com/pdf114514/CarReservation/sessions/1d8c6b05-0e8d-4484-a2d8-8d427dfad9cb
This commit is contained in:
191
frontend/src/components/CarManagement.jsx
Normal file
191
frontend/src/components/CarManagement.jsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { api } from '../api.js';
|
||||
import styles from './CarManagement.module.css';
|
||||
|
||||
export default function CarManagement() {
|
||||
const [cars, setCars] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [newCarName, setNewCarName] = useState('');
|
||||
const [newCarDesc, setNewCarDesc] = useState('');
|
||||
const [editingId, setEditingId] = useState(null);
|
||||
const [editName, setEditName] = useState('');
|
||||
const [editDesc, setEditDesc] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const loadCars = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await api.getCars();
|
||||
setCars(data);
|
||||
setError(null);
|
||||
} catch (e) {
|
||||
setError(e.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadCars();
|
||||
}, []);
|
||||
|
||||
const handleAdd = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!newCarName.trim()) return;
|
||||
try {
|
||||
setSubmitting(true);
|
||||
await api.createCar({ name: newCarName.trim(), description: newCarDesc.trim() });
|
||||
setNewCarName('');
|
||||
setNewCarDesc('');
|
||||
await loadCars();
|
||||
} catch (e) {
|
||||
setError(e.message);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id, name) => {
|
||||
if (!confirm(`「${name}」を削除しますか?\n関連する予約もすべて削除されます。`)) return;
|
||||
try {
|
||||
await api.deleteCar(id);
|
||||
await loadCars();
|
||||
} catch (e) {
|
||||
setError(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const startEdit = (car) => {
|
||||
setEditingId(car.id);
|
||||
setEditName(car.name);
|
||||
setEditDesc(car.description || '');
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
setEditingId(null);
|
||||
setEditName('');
|
||||
setEditDesc('');
|
||||
};
|
||||
|
||||
const handleUpdate = async (id) => {
|
||||
if (!editName.trim()) return;
|
||||
try {
|
||||
setSubmitting(true);
|
||||
await api.updateCar(id, { name: editName.trim(), description: editDesc.trim() });
|
||||
cancelEdit();
|
||||
await loadCars();
|
||||
} catch (e) {
|
||||
setError(e.message);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<h2 className={styles.heading}>代車管理</h2>
|
||||
|
||||
<div className={styles.addCard}>
|
||||
<h3 className={styles.subHeading}>代車を追加</h3>
|
||||
<form className={styles.form} onSubmit={handleAdd}>
|
||||
<div className={styles.formRow}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
placeholder="車名(例:プリウス A)"
|
||||
value={newCarName}
|
||||
onChange={(e) => setNewCarName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
placeholder="備考(任意)"
|
||||
value={newCarDesc}
|
||||
onChange={(e) => setNewCarDesc(e.target.value)}
|
||||
/>
|
||||
<button type="submit" className={styles.btnPrimary} disabled={submitting || !newCarName.trim()}>
|
||||
+ 追加
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{loading && <p className={styles.message}>読み込み中...</p>}
|
||||
{error && <p className={styles.error}>エラー: {error}</p>}
|
||||
|
||||
{!loading && !error && (
|
||||
<div className={styles.tableWrapper}>
|
||||
<table className={styles.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>車名</th>
|
||||
<th>備考</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{cars.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={4} className={styles.empty}>代車がありません</td>
|
||||
</tr>
|
||||
)}
|
||||
{cars.map((car) => (
|
||||
<tr key={car.id}>
|
||||
<td className={styles.idCell}>{car.id}</td>
|
||||
{editingId === car.id ? (
|
||||
<>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
value={editName}
|
||||
onChange={(e) => setEditName(e.target.value)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
value={editDesc}
|
||||
onChange={(e) => setEditDesc(e.target.value)}
|
||||
/>
|
||||
</td>
|
||||
<td className={styles.actions}>
|
||||
<button
|
||||
className={styles.btnSave}
|
||||
onClick={() => handleUpdate(car.id)}
|
||||
disabled={submitting}
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
<button className={styles.btnCancel} onClick={cancelEdit}>
|
||||
キャンセル
|
||||
</button>
|
||||
</td>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<td>{car.name}</td>
|
||||
<td className={styles.descCell}>{car.description || '-'}</td>
|
||||
<td className={styles.actions}>
|
||||
<button className={styles.btnEdit} onClick={() => startEdit(car)}>
|
||||
編集
|
||||
</button>
|
||||
<button className={styles.btnDelete} onClick={() => handleDelete(car.id, car.name)}>
|
||||
削除
|
||||
</button>
|
||||
</td>
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user