mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 21:31:46 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48508b9df4 | |||
| c8250256a7 | |||
| 6491e1f986 | |||
| 03757ed0af | |||
| a676dbe881 | |||
| 411d8620ba |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "trek-client",
|
||||
"version": "2.9.2",
|
||||
"version": "2.9.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "trek-client",
|
||||
"version": "2.9.2",
|
||||
"version": "2.9.5",
|
||||
"dependencies": {
|
||||
"@react-pdf/renderer": "^4.3.2",
|
||||
"axios": "^1.6.7",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trek-client",
|
||||
"version": "2.9.2",
|
||||
"version": "2.9.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -248,7 +248,7 @@ export const reservationsApi = {
|
||||
create: (tripId: number | string, data: Record<string, unknown>) => apiClient.post(`/trips/${tripId}/reservations`, data).then(r => r.data),
|
||||
update: (tripId: number | string, id: number, data: Record<string, unknown>) => apiClient.put(`/trips/${tripId}/reservations/${id}`, data).then(r => r.data),
|
||||
delete: (tripId: number | string, id: number) => apiClient.delete(`/trips/${tripId}/reservations/${id}`).then(r => r.data),
|
||||
updatePositions: (tripId: number | string, positions: { id: number; day_plan_position: number }[]) => apiClient.put(`/trips/${tripId}/reservations/positions`, { positions }).then(r => r.data),
|
||||
updatePositions: (tripId: number | string, positions: { id: number; day_plan_position: number }[], dayId?: number) => apiClient.put(`/trips/${tripId}/reservations/positions`, { positions, day_id: dayId }).then(r => r.data),
|
||||
}
|
||||
|
||||
export const weatherApi = {
|
||||
|
||||
@@ -366,9 +366,12 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({
|
||||
const timed = timedTransports[ti]
|
||||
const minutes = timed.minutes
|
||||
|
||||
// Use persisted position if available
|
||||
if (timed.data.day_plan_position != null) {
|
||||
result.push({ type: timed.type, sortKey: timed.data.day_plan_position, data: timed.data })
|
||||
// Use per-day position if available, fallback to global position
|
||||
const dayObj = days.find(d => d.id === dayId)
|
||||
const perDayPos = timed.data.day_positions?.[dayId] ?? timed.data.day_positions?.[String(dayId)]
|
||||
const effectivePos = perDayPos ?? timed.data.day_plan_position
|
||||
if (effectivePos != null) {
|
||||
result.push({ type: timed.type, sortKey: effectivePos, data: timed.data })
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -500,10 +503,15 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({
|
||||
if (transportUpdates.length) {
|
||||
for (const tu of transportUpdates) {
|
||||
const res = reservations.find(r => r.id === tu.id)
|
||||
if (res) res.day_plan_position = tu.day_plan_position
|
||||
if (res) {
|
||||
res.day_plan_position = tu.day_plan_position
|
||||
// Update per-day position for multi-day reservations
|
||||
if (!res.day_positions) res.day_positions = {}
|
||||
res.day_positions[dayId] = tu.day_plan_position
|
||||
}
|
||||
}
|
||||
setTransportPosVersion(v => v + 1)
|
||||
await reservationsApi.updatePositions(tripId, transportUpdates)
|
||||
await reservationsApi.updatePositions(tripId, transportUpdates, dayId)
|
||||
}
|
||||
if (prevAssignmentIds.length) {
|
||||
const capturedDayId = dayId
|
||||
|
||||
@@ -141,7 +141,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
|
||||
hotel_start_day: (() => { const acc = accommodations.find(a => a.id == reservation.accommodation_id); return acc?.start_day_id || '' })(),
|
||||
hotel_end_day: (() => { const acc = accommodations.find(a => a.id == reservation.accommodation_id); return acc?.end_day_id || '' })(),
|
||||
price: meta.price || '',
|
||||
budget_category: meta.budget_category || '',
|
||||
budget_category: (meta.budget_category && budgetItems.some(i => i.category === meta.budget_category)) ? meta.budget_category : '',
|
||||
})
|
||||
} else {
|
||||
setForm({
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "trek-server",
|
||||
"version": "2.9.2",
|
||||
"version": "2.9.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "trek-server",
|
||||
"version": "2.9.2",
|
||||
"version": "2.9.5",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.28.0",
|
||||
"archiver": "^6.0.1",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trek-server",
|
||||
"version": "2.9.2",
|
||||
"version": "2.9.5",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"start": "node --import tsx src/index.ts",
|
||||
|
||||
@@ -843,6 +843,27 @@ function runMigrations(db: Database.Database): void {
|
||||
const ins = db.prepare('INSERT OR IGNORE INTO packing_bag_members (bag_id, user_id) VALUES (?, ?)');
|
||||
for (const b of bagsWithUser) ins.run(b.id, b.user_id);
|
||||
},
|
||||
// Migration: Per-day positions for multi-day reservations
|
||||
() => {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS reservation_day_positions (
|
||||
reservation_id INTEGER NOT NULL REFERENCES reservations(id) ON DELETE CASCADE,
|
||||
day_id INTEGER NOT NULL REFERENCES days(id) ON DELETE CASCADE,
|
||||
position REAL NOT NULL,
|
||||
PRIMARY KEY (reservation_id, day_id)
|
||||
);
|
||||
`);
|
||||
// Migrate existing global positions to per-day entries
|
||||
const reservations = db.prepare('SELECT id, trip_id, reservation_time, reservation_end_time, day_plan_position FROM reservations WHERE day_plan_position IS NOT NULL').all() as any[];
|
||||
const ins = db.prepare('INSERT OR IGNORE INTO reservation_day_positions (reservation_id, day_id, position) VALUES (?, ?, ?)');
|
||||
for (const r of reservations) {
|
||||
const startDate = r.reservation_time?.split('T')[0];
|
||||
const endDate = r.reservation_end_time?.split('T')[0] || startDate;
|
||||
if (!startDate) continue;
|
||||
const matchingDays = db.prepare('SELECT id FROM days WHERE trip_id = ? AND date >= ? AND date <= ?').all(r.trip_id, startDate, endDate) as { id: number }[];
|
||||
for (const d of matchingDays) ins.run(r.id, d.id, r.day_plan_position);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
if (currentVersion < migrations.length) {
|
||||
|
||||
@@ -91,10 +91,11 @@ router.put('/positions', authenticate, (req: Request, res: Response) => {
|
||||
|
||||
if (!Array.isArray(positions)) return res.status(400).json({ error: 'positions must be an array' });
|
||||
|
||||
updatePositions(tripId, positions);
|
||||
const { day_id } = req.body;
|
||||
updatePositions(tripId, positions, day_id);
|
||||
|
||||
res.json({ success: true });
|
||||
broadcast(tripId, 'reservation:positions', { positions }, req.headers['x-socket-id'] as string);
|
||||
broadcast(tripId, 'reservation:positions', { positions, day_id }, req.headers['x-socket-id'] as string);
|
||||
});
|
||||
|
||||
router.put('/:id', authenticate, (req: Request, res: Response) => {
|
||||
|
||||
@@ -178,7 +178,10 @@ export async function pipeAsset(url: string, response: Response, headers?: Recor
|
||||
await pipeline(Readable.fromWeb(resp.body as any), response);
|
||||
}
|
||||
} catch (error) {
|
||||
if (response.headersSent) return;
|
||||
if (response.headersSent) {
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
if (error instanceof SsrfBlockedError) {
|
||||
response.status(400).json({ error: error.message });
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@ export function verifyTripAccess(tripId: string | number, userId: number) {
|
||||
}
|
||||
|
||||
export function listReservations(tripId: string | number) {
|
||||
return db.prepare(`
|
||||
const reservations = db.prepare(`
|
||||
SELECT r.*, d.day_number, p.name as place_name, r.assignment_id,
|
||||
ap.place_id as accommodation_place_id, acc_p.name as accommodation_name
|
||||
FROM reservations r
|
||||
@@ -16,7 +16,27 @@ export function listReservations(tripId: string | number) {
|
||||
LEFT JOIN places acc_p ON ap.place_id = acc_p.id
|
||||
WHERE r.trip_id = ?
|
||||
ORDER BY r.reservation_time ASC, r.created_at ASC
|
||||
`).all(tripId);
|
||||
`).all(tripId) as any[];
|
||||
|
||||
// Attach per-day positions for multi-day reservations
|
||||
const dayPositions = db.prepare(`
|
||||
SELECT rdp.reservation_id, rdp.day_id, rdp.position
|
||||
FROM reservation_day_positions rdp
|
||||
JOIN reservations r ON rdp.reservation_id = r.id
|
||||
WHERE r.trip_id = ?
|
||||
`).all(tripId) as { reservation_id: number; day_id: number; position: number }[];
|
||||
|
||||
const posMap = new Map<number, Record<number, number>>();
|
||||
for (const dp of dayPositions) {
|
||||
if (!posMap.has(dp.reservation_id)) posMap.set(dp.reservation_id, {});
|
||||
posMap.get(dp.reservation_id)![dp.day_id] = dp.position;
|
||||
}
|
||||
|
||||
for (const r of reservations) {
|
||||
r.day_positions = posMap.get(r.id) || null;
|
||||
}
|
||||
|
||||
return reservations;
|
||||
}
|
||||
|
||||
export function getReservationWithJoins(id: string | number) {
|
||||
@@ -117,14 +137,35 @@ export function createReservation(tripId: string | number, data: CreateReservati
|
||||
return { reservation, accommodationCreated };
|
||||
}
|
||||
|
||||
export function updatePositions(tripId: string | number, positions: { id: number; day_plan_position: number }[]) {
|
||||
const stmt = db.prepare('UPDATE reservations SET day_plan_position = ? WHERE id = ? AND trip_id = ?');
|
||||
const updateMany = db.transaction((items: { id: number; day_plan_position: number }[]) => {
|
||||
for (const item of items) {
|
||||
stmt.run(item.day_plan_position, item.id, tripId);
|
||||
}
|
||||
});
|
||||
updateMany(positions);
|
||||
export function updatePositions(tripId: string | number, positions: { id: number; day_plan_position: number }[], dayId?: number | string) {
|
||||
if (dayId) {
|
||||
// Per-day positions for multi-day reservations
|
||||
const stmt = db.prepare('INSERT OR REPLACE INTO reservation_day_positions (reservation_id, day_id, position) VALUES (?, ?, ?)');
|
||||
const updateMany = db.transaction((items: { id: number; day_plan_position: number }[]) => {
|
||||
for (const item of items) {
|
||||
stmt.run(item.id, dayId, item.day_plan_position);
|
||||
}
|
||||
});
|
||||
updateMany(positions);
|
||||
} else {
|
||||
// Legacy: update global position
|
||||
const stmt = db.prepare('UPDATE reservations SET day_plan_position = ? WHERE id = ? AND trip_id = ?');
|
||||
const updateMany = db.transaction((items: { id: number; day_plan_position: number }[]) => {
|
||||
for (const item of items) {
|
||||
stmt.run(item.day_plan_position, item.id, tripId);
|
||||
}
|
||||
});
|
||||
updateMany(positions);
|
||||
}
|
||||
}
|
||||
|
||||
export function getDayPositions(tripId: string | number, dayId: number | string) {
|
||||
return db.prepare(`
|
||||
SELECT rdp.reservation_id, rdp.position
|
||||
FROM reservation_day_positions rdp
|
||||
JOIN reservations r ON rdp.reservation_id = r.id
|
||||
WHERE r.trip_id = ? AND rdp.day_id = ?
|
||||
`).all(tripId, dayId) as { reservation_id: number; position: number }[];
|
||||
}
|
||||
|
||||
export function getReservation(id: string | number, tripId: string | number) {
|
||||
|
||||
Reference in New Issue
Block a user