Disable touch drag & drop, warn on car delete with reservations, configure backend URL

Co-authored-by: pdf114514 <57948770+pdf114514@users.noreply.github.com>
Agent-Logs-Url: https://github.com/pdf114514/CarReservation/sessions/cd194ca1-b339-4f2f-b717-31a0ba193964
This commit is contained in:
copilot-swe-agent[bot]
2026-03-20 18:19:53 +00:00
parent 40371b43d1
commit c3dd0cfa69
7 changed files with 58 additions and 8 deletions

View File

@@ -7,7 +7,6 @@
"": {
"name": "frontend",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@vitejs/plugin-react": "^6.0.1",
"date-fns": "^4.1.0",

View File

@@ -1,4 +1,4 @@
const API_BASE = '/api';
const API_BASE = (import.meta.env.VITE_API_BASE_URL ?? '') + '/api';
async function request(path, options = {}) {
const res = await fetch(`${API_BASE}${path}`, {

View File

@@ -4,6 +4,7 @@ import styles from './CarManagement.module.css';
export default function CarManagement() {
const [cars, setCars] = useState([]);
const [reservations, setReservations] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [newCarName, setNewCarName] = useState('');
@@ -16,8 +17,9 @@ export default function CarManagement() {
const loadCars = async () => {
try {
setLoading(true);
const data = await api.getCars();
setCars(data);
const [carsData, resData] = await Promise.all([api.getCars(), api.getReservations()]);
setCars(carsData);
setReservations(resData);
setError(null);
} catch (e) {
setError(e.message);
@@ -47,7 +49,11 @@ export default function CarManagement() {
};
const handleDelete = async (id, name) => {
if (!confirm(`${name}」を削除しますか?\n関連する予約もすべて削除されます。`)) return;
const carReservations = reservations.filter((r) => r.car_id === id);
const message = carReservations.length > 0
? `${name}」を削除しますか?\n⚠ この代車には ${carReservations.length} 件の予約があります。削除するとこれらの予約もすべて削除されます。`
: `${name}」を削除しますか?\n関連する予約もすべて削除されます。`;
if (!confirm(message)) return;
try {
await api.deleteCar(id);
await loadCars();

View File

@@ -11,6 +11,10 @@ const LABEL_WIDTH = 140; // px for car name column
const HEADER_HEIGHT = 72; // px for the date header row
const DAYS_SHOWN = 21; // number of days to show
// Detect touch-primary device to disable mouse-only drag & drop
const isTouchDevice = typeof window !== 'undefined' &&
('ontouchstart' in window || navigator.maxTouchPoints > 0);
// Palette for reservation colors (cycle through them by car index)
const COLORS = [
{ bg: '#dbeafe', border: '#3b82f6', text: '#1e3a8a' },
@@ -106,11 +110,25 @@ export default function ScheduleView() {
// --- Cell drag to create ---
const handleCellMouseDown = (e, carId, dateStr) => {
if (isTouchDevice) return; // drag-to-create is mouse-only
if (e.button !== 0) return;
e.preventDefault();
setCreating({ carId, startDateStr: dateStr, endDateStr: dateStr });
};
// --- Cell tap to create (touch devices) ---
const handleCellClick = useCallback((e, carId) => {
if (!isTouchDevice) return;
const col = getColFromX(e.clientX);
if (col >= 0 && col < DAYS_SHOWN) {
const dateStr = dateToStr(dates[col]);
setModal({
mode: 'create',
prefill: { car_id: carId, start_date: dateStr, end_date: dateStr },
});
}
}, [dates, getColFromX]);
const handleGridMouseMove = useCallback((e) => {
if (creating) {
const col = getColFromX(e.clientX);
@@ -223,6 +241,7 @@ export default function ScheduleView() {
// --- Reservation drag to move ---
const handleReservationMouseDown = (e, reservation) => {
e.stopPropagation();
if (isTouchDevice) return; // drag-to-move is mouse-only
if (e.button !== 0) return;
e.preventDefault();
@@ -413,6 +432,7 @@ export default function ScheduleView() {
<div
className={styles.cellArea}
style={{ width: DAYS_SHOWN * CELL_WIDTH, height: ROW_HEIGHT }}
onClick={(e) => handleCellClick(e, car.id)}
>
{dates.map((date) => {
const ds = dateToStr(date);
@@ -422,7 +442,7 @@ export default function ScheduleView() {
return (
<div
key={ds}
className={`${styles.cell} ${isToday ? styles.todayCell : ''} ${isWeekend ? styles.weekendCell : ''}`}
className={`${styles.cell} ${isToday ? styles.todayCell : ''} ${isWeekend ? styles.weekendCell : ''} ${isTouchDevice ? styles.cellTouch : ''}`}
style={{ width: CELL_WIDTH, height: ROW_HEIGHT }}
onMouseDown={(e) => handleCellMouseDown(e, car.id, ds)}
/>
@@ -479,7 +499,7 @@ export default function ScheduleView() {
background: color.bg,
borderColor: color.border,
color: color.text,
cursor: 'grab',
cursor: isTouchDevice ? 'pointer' : 'grab',
}}
onMouseDown={(e) => handleReservationMouseDown(e, r)}
onClick={(e) => {

View File

@@ -198,6 +198,10 @@
transition: background 0.1s;
}
.cellTouch {
cursor: pointer;
}
.cell:hover {
background: rgba(26, 86, 219, 0.04);
}

View File

@@ -7,7 +7,7 @@ export default defineConfig({
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:3001',
target: process.env.BACKEND_URL || 'http://localhost:3001',
changeOrigin: true,
},
},