From 40371b43d102632cd3fed1e7168a7792890c5fa7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 18:13:46 +0000 Subject: [PATCH 1/2] Initial plan From c3dd0cfa69589f3c491ca8c9732f738a1a9de34a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 18:19:53 +0000 Subject: [PATCH 2/2] 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 --- README.md | 21 ++++++++++++++++ frontend/package-lock.json | 1 - frontend/src/api.js | 2 +- frontend/src/components/CarManagement.jsx | 12 +++++++--- frontend/src/components/ScheduleView.jsx | 24 +++++++++++++++++-- .../src/components/ScheduleView.module.css | 4 ++++ frontend/vite.config.js | 2 +- 7 files changed, 58 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 54474ee..fb97daa 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,27 @@ npm run dev Open http://localhost:5173 in your browser. +### Configuration + +#### Backend URL (development proxy) + +By default the Vite dev server proxies `/api` requests to `http://localhost:3001`. +To point to a different backend server, set the `BACKEND_URL` environment variable when starting the frontend: + +```bash +BACKEND_URL=http://192.168.1.10:3001 npm run dev:frontend +``` + +#### Backend URL (production build) + +When the frontend is deployed separately from the backend, set `VITE_API_BASE_URL` to the backend server's origin before building: + +```bash +VITE_API_BASE_URL=https://api.example.com npm run build +``` + +This makes the built frontend send API requests to `https://api.example.com/api`. + ### API Endpoints | Method | Path | Description | diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 969105b..4110f9c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,7 +7,6 @@ "": { "name": "frontend", "version": "1.0.0", - "license": "ISC", "dependencies": { "@vitejs/plugin-react": "^6.0.1", "date-fns": "^4.1.0", diff --git a/frontend/src/api.js b/frontend/src/api.js index 7cd079f..55a7aa5 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -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}`, { diff --git a/frontend/src/components/CarManagement.jsx b/frontend/src/components/CarManagement.jsx index 77a8f33..76f593f 100644 --- a/frontend/src/components/CarManagement.jsx +++ b/frontend/src/components/CarManagement.jsx @@ -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(); diff --git a/frontend/src/components/ScheduleView.jsx b/frontend/src/components/ScheduleView.jsx index e1d1523..a071a3f 100644 --- a/frontend/src/components/ScheduleView.jsx +++ b/frontend/src/components/ScheduleView.jsx @@ -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() {