Add timeline view, right-click context menu, and fix Express trust proxy
- backend/server.js: Add app.set('trust proxy', 1) to fix express-rate-limit
ValidationError when app runs behind nginx reverse proxy
- ScheduleView.jsx: Add right-click context menu on reservation blocks with
Edit and Delete options; closes on click-outside or Escape
- ScheduleView.module.css: Add context menu styles
- TimelineView.jsx: New Gantt-style monthly timeline view showing all
reservations sorted by date, with month navigation and right-click menu
- TimelineView.module.css: Styles for the timeline view
- App.jsx: Add 'タイムライン' tab to navigation
Co-authored-by: pdf114514 <57948770+pdf114514@users.noreply.github.com>
Agent-Logs-Url: https://github.com/pdf114514/CarReservation/sessions/d03ca12c-21ce-45a0-881f-919d6635e7fb
This commit is contained in:
@@ -58,6 +58,10 @@ export default function ScheduleView() {
|
||||
const [modal, setModal] = useState(null);
|
||||
// null | { mode: 'create', prefill: {...} } | { mode: 'edit', reservation: {...} }
|
||||
|
||||
// Context menu state (right-click on reservation)
|
||||
const [contextMenu, setContextMenu] = useState(null);
|
||||
// null | { x, y, reservation }
|
||||
|
||||
const gridRef = useRef(null);
|
||||
const movingRef = useRef(null); // keeps latest moving state for event handlers
|
||||
|
||||
@@ -238,6 +242,19 @@ export default function ScheduleView() {
|
||||
};
|
||||
}, [handleGridMouseMove, handleGridMouseUp]);
|
||||
|
||||
// Close context menu on any click or Escape
|
||||
useEffect(() => {
|
||||
if (!contextMenu) return;
|
||||
const close = () => setContextMenu(null);
|
||||
const onKey = (e) => { if (e.key === 'Escape') close(); };
|
||||
window.addEventListener('click', close);
|
||||
window.addEventListener('keydown', onKey);
|
||||
return () => {
|
||||
window.removeEventListener('click', close);
|
||||
window.removeEventListener('keydown', onKey);
|
||||
};
|
||||
}, [contextMenu]);
|
||||
|
||||
// --- Reservation drag to move ---
|
||||
const handleReservationMouseDown = (e, reservation) => {
|
||||
e.stopPropagation();
|
||||
@@ -508,6 +525,11 @@ export default function ScheduleView() {
|
||||
setModal({ mode: 'edit', reservation: r });
|
||||
}
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setContextMenu({ x: e.clientX, y: e.clientY, reservation: r });
|
||||
}}
|
||||
title={`${r.customer_name || '予約'}\n${r.start_date} 〜 ${r.end_date}${r.notes ? '\n' + r.notes : ''}`}
|
||||
>
|
||||
<span className={styles.blockText}>
|
||||
@@ -544,6 +566,34 @@ export default function ScheduleView() {
|
||||
onClose={() => setModal(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Right-click context menu */}
|
||||
{contextMenu && (
|
||||
<div
|
||||
className={styles.contextMenu}
|
||||
style={{ left: contextMenu.x, top: contextMenu.y }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
className={styles.contextMenuItem}
|
||||
onClick={() => {
|
||||
setModal({ mode: 'edit', reservation: contextMenu.reservation });
|
||||
setContextMenu(null);
|
||||
}}
|
||||
>
|
||||
✏️ 編集
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.contextMenuItem} ${styles.contextMenuItemDelete}`}
|
||||
onClick={async () => {
|
||||
setContextMenu(null);
|
||||
await handleModalDelete(contextMenu.reservation.id);
|
||||
}}
|
||||
>
|
||||
🗑️ 削除
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user