diff --git a/backend/server.js b/backend/server.js index d50df2b..0e76df0 100644 --- a/backend/server.js +++ b/backend/server.js @@ -9,6 +9,61 @@ const app = express() app.use(cors()) app.use(express.json()) +// Helper: normalize booking row fields to stable strings (avoid timezone surprises) +function sanitizeBooking(row) { + const normDate = (v) => { + if (!v) return v + if (typeof v === 'string') { + // If it's a plain ISO date (no time), keep as-is + const exact = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v) + if (exact) return `${exact[1]}-${exact[2]}-${exact[3]}` + // If string contains time part, parse and convert to local date components + if (v.includes('T')) { + const d = new Date(v) + if (!isNaN(d.getTime())) { + const yyyy = d.getFullYear() + const mm = String(d.getMonth() + 1).padStart(2, '0') + const dd = String(d.getDate()).padStart(2, '0') + return `${yyyy}-${mm}-${dd}` + } + } + // Try generic parse fallback + const d = new Date(v) + if (!isNaN(d.getTime())) { + const yyyy = d.getFullYear() + const mm = String(d.getMonth() + 1).padStart(2, '0') + const dd = String(d.getDate()).padStart(2, '0') + return `${yyyy}-${mm}-${dd}` + } + return v + } + if (v instanceof Date) { + const yyyy = v.getFullYear() + const mm = String(v.getMonth() + 1).padStart(2, '0') + const dd = String(v.getDate()).padStart(2, '0') + return `${yyyy}-${mm}-${dd}` + } + return v + } + const normTime = (v) => { + if (!v) return v + if (typeof v === 'string') { + // Expect 'HH:MM:SS' or 'HH:MM' + const parts = v.split(':') + if (parts.length >= 2) return `${parts[0].padStart(2,'0')}:${parts[1].padStart(2,'0')}` + return v + } + return v + } + return { + ...row, + start_date: normDate(row.start_date), + end_date: normDate(row.end_date), + start_time: normTime(row.start_time), + end_time: normTime(row.end_time), + } +} + // Simple health app.get('/api/health', (req, res) => res.json({ ok: true })) @@ -36,7 +91,7 @@ app.post('/api/bookings', async (req, res) => { VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *` const values = [single, multi.length ? multi.join(',') : null, start_date, start_time || null, end_date, end_time || null, name, email, 'pending'] const result = await pool.query(text, values) - res.status(201).json({ booking: result.rows[0] }) + res.status(201).json({ booking: sanitizeBooking(result.rows[0]) }) } catch (err) { console.error(err) res.status(500).json({ error: 'Server error' }) @@ -47,7 +102,7 @@ app.post('/api/bookings', async (req, res) => { app.get('/api/bookings', async (req, res) => { try { const result = await pool.query('SELECT * FROM bookings ORDER BY created_at DESC LIMIT 100') - res.json({ bookings: result.rows }) + res.json({ bookings: result.rows.map(sanitizeBooking) }) } catch (err) { console.error(err) res.status(500).json({ error: 'Server error' }) @@ -75,7 +130,7 @@ app.patch('/api/bookings/:id/status', async (req, res) => { const result = await pool.query('UPDATE bookings SET status = $1 WHERE id = $2 RETURNING *', [status, id]) if (result.rowCount === 0) return res.status(404).json({ error: 'Booking not found' }) - res.json({ booking: result.rows[0] }) + res.json({ booking: sanitizeBooking(result.rows[0]) }) } catch (err) { console.error(err) res.status(500).json({ error: 'Server error' }) @@ -89,7 +144,7 @@ app.patch('/api/bookings/:id', async (req, res) => { if (!Number.isFinite(id)) return res.status(400).json({ error: 'Invalid id' }) const allowed = ['bike_type', 'bike_types', 'start_date', 'start_time', 'end_date', 'end_time', 'name', 'email'] - const entries = Object.entries(req.body || {}).filter(([k, v]) => allowed.includes(k)) + const entries = Object.entries(req.body || {}).filter(([k]) => allowed.includes(k)) if (entries.length === 0) return res.status(400).json({ error: 'No valid fields to update' }) @@ -107,7 +162,7 @@ app.patch('/api/bookings/:id', async (req, res) => { const result = await pool.query(sql, values) if (result.rowCount === 0) return res.status(404).json({ error: 'Booking not found' }) - res.json({ booking: result.rows[0] }) + res.json({ booking: sanitizeBooking(result.rows[0]) }) } catch (err) { console.error(err) res.status(500).json({ error: 'Server error' }) @@ -123,7 +178,7 @@ app.delete('/api/bookings/:id', async (req, res) => { const result = await pool.query('DELETE FROM bookings WHERE id = $1 RETURNING *', [id]) if (result.rowCount === 0) return res.status(404).json({ error: 'Booking not found' }) - res.json({ ok: true, booking: result.rows[0] }) + res.json({ ok: true, booking: sanitizeBooking(result.rows[0]) }) } catch (err) { console.error(err) res.status(500).json({ error: 'Server error' }) diff --git a/src/App.vue b/src/App.vue index 546cf61..ffb8582 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,13 +1,14 @@