diff --git a/README.md b/README.md index e5d768a9..49d844fd 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ services: # - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist # - ADMIN_PASSWORD=changeme # Initial admin password — only used on first boot when no users exist # - MCP_RATE_LIMIT=60 # Max MCP API requests per user per minute (default: 60) + # - MCP_MAX_SESSION_PER_USER=5 # Max concurrent MCP sessions per user (default: 5) volumes: - ./data:/app/data - ./uploads:/app/uploads @@ -303,6 +304,7 @@ trek.yourdomain.com { | **Other** | | | | `DEMO_MODE` | Enable demo mode (hourly data resets) | `false` | | `MCP_RATE_LIMIT` | Max MCP API requests per user per minute | `60` | +| `MCP_MAX_SESSION_PER_USER` | Max concurrent MCP sessions per user | `5` | ## Optional API Keys diff --git a/chart/values.yaml b/chart/values.yaml index 52dfccf7..47a941c7 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -53,6 +53,8 @@ env: # Enable demo mode (hourly data resets). # MCP_RATE_LIMIT: "60" # Max MCP API requests per user per minute. Defaults to 60. + # MCP_MAX_SESSION_PER_USER: "5" + # Max concurrent MCP sessions per user. Defaults to 5. # Secret environment variables stored in a Kubernetes Secret. diff --git a/client/package-lock.json b/client/package-lock.json index 18078911..5a2810af 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "trek-client", - "version": "2.9.7", + "version": "2.9.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trek-client", - "version": "2.9.7", + "version": "2.9.9", "dependencies": { "@react-pdf/renderer": "^4.3.2", "axios": "^1.6.7", diff --git a/client/package.json b/client/package.json index 0462203b..cd2fe60b 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "trek-client", - "version": "2.9.7", + "version": "2.9.9", "private": true, "type": "module", "scripts": { diff --git a/client/src/components/Planner/ReservationModal.tsx b/client/src/components/Planner/ReservationModal.tsx index d974d6a8..cbbfba68 100644 --- a/client/src/components/Planner/ReservationModal.tsx +++ b/client/src/components/Planner/ReservationModal.tsx @@ -10,6 +10,7 @@ import { useToast } from '../shared/Toast' import { useTranslation } from '../../i18n' import { CustomDatePicker } from '../shared/CustomDateTimePicker' import CustomTimePicker from '../shared/CustomTimePicker' +import { getAuthUrl } from '../../api/authUrl' import type { Day, Place, Reservation, TripFile, AssignmentsMap, Accommodation } from '../../types' const TYPE_OPTIONS = [ @@ -113,6 +114,9 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p if (rawEnd.includes('T')) { endDate = rawEnd.split('T')[0] endTime = rawEnd.split('T')[1]?.slice(0, 5) || '' + } else if (/^\d{4}-\d{2}-\d{2}$/.test(rawEnd)) { + endDate = rawEnd + endTime = '' } setForm({ title: reservation.title || '', @@ -166,6 +170,22 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p const startDate = form.reservation_time.split('T')[0] const startTime = form.reservation_time.split('T')[1] || '00:00' const endTime = form.reservation_end_time || '00:00' + // For flights, compare in UTC using timezone offsets + if (form.type === 'flight') { + const parseOffset = (tz: string): number | null => { + if (!tz) return null + const m = tz.trim().match(/^(?:UTC|GMT)?\s*([+-])(\d{1,2})(?::(\d{2}))?$/i) + if (!m) return null + const sign = m[1] === '+' ? 1 : -1 + return sign * (parseInt(m[2]) * 60 + parseInt(m[3] || '0')) + } + const depOffset = parseOffset(form.meta_departure_timezone) + const arrOffset = parseOffset(form.meta_arrival_timezone) + if (depOffset === null || arrOffset === null) return false + const depMinutes = new Date(`${startDate}T${startTime}`).getTime() - depOffset * 60000 + const arrMinutes = new Date(`${form.end_date}T${endTime}`).getTime() - arrOffset * 60000 + return arrMinutes <= depMinutes + } const startFull = `${startDate}T${startTime}` const endFull = `${form.end_date}T${endTime}` return endFull <= startFull @@ -204,7 +224,8 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p } const saveData: Record = { title: form.title, type: form.type, status: form.status, - reservation_time: form.reservation_time, reservation_end_time: combinedEndTime, + reservation_time: form.type === 'hotel' ? null : form.reservation_time, + reservation_end_time: form.type === 'hotel' ? null : combinedEndTime, location: form.location, confirmation_number: form.confirmation_number, notes: form.notes, assignment_id: form.assignment_id || null, @@ -565,7 +586,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
{f.original_name} - + { e.preventDefault(); const u = await getAuthUrl(f.url, 'download'); window.open(u, '_blank', 'noreferrer') }} style={{ color: 'var(--text-faint)', display: 'flex', flexShrink: 0, cursor: 'pointer' }}>