feat: bucket list POIs with auto-search + optional dates — closes #105

- Bucket list now supports POIs (not just countries): add any place
  with auto-search via Google Places / Nominatim
- Optional target date (month/year) via CustomSelect dropdowns
- New target_date field on bucket_list table (DB migration)
- Server PUT route supports updating all fields
- Country bucket modal: date dropdowns default to empty
- CustomSelect: auto-opens upward when near bottom of viewport
- Search results open upward in the bucket add form
- i18n keys for DE and EN
This commit is contained in:
Maurice
2026-03-30 14:57:31 +02:00
parent 949d0967d2
commit a6a7edf0b2
6 changed files with 195 additions and 26 deletions

View File

@@ -329,6 +329,10 @@ function runMigrations(db: Database.Database): void {
// Add paid_by_user_id to budget_items for expense tracking / settlement
try { db.exec('ALTER TABLE budget_items ADD COLUMN paid_by_user_id INTEGER REFERENCES users(id)'); } catch {}
},
() => {
// Add target_date to bucket_list for optional visit planning
try { db.exec('ALTER TABLE bucket_list ADD COLUMN target_date TEXT DEFAULT NULL'); } catch {}
},
];
if (currentVersion < migrations.length) {

View File

@@ -277,10 +277,10 @@ router.get('/bucket-list', (req: Request, res: Response) => {
router.post('/bucket-list', (req: Request, res: Response) => {
const authReq = req as AuthRequest;
const { name, lat, lng, country_code, notes } = req.body;
const { name, lat, lng, country_code, notes, target_date } = req.body;
if (!name?.trim()) return res.status(400).json({ error: 'Name is required' });
const result = db.prepare('INSERT INTO bucket_list (user_id, name, lat, lng, country_code, notes) VALUES (?, ?, ?, ?, ?, ?)').run(
authReq.user.id, name.trim(), lat ?? null, lng ?? null, country_code ?? null, notes ?? null
const result = db.prepare('INSERT INTO bucket_list (user_id, name, lat, lng, country_code, notes, target_date) VALUES (?, ?, ?, ?, ?, ?, ?)').run(
authReq.user.id, name.trim(), lat ?? null, lng ?? null, country_code ?? null, notes ?? null, target_date ?? null
);
const item = db.prepare('SELECT * FROM bucket_list WHERE id = ?').get(result.lastInsertRowid);
res.status(201).json({ item });
@@ -288,10 +288,25 @@ router.post('/bucket-list', (req: Request, res: Response) => {
router.put('/bucket-list/:id', (req: Request, res: Response) => {
const authReq = req as AuthRequest;
const { name, notes } = req.body;
const { name, notes, lat, lng, country_code, target_date } = req.body;
const item = db.prepare('SELECT * FROM bucket_list WHERE id = ? AND user_id = ?').get(req.params.id, authReq.user.id);
if (!item) return res.status(404).json({ error: 'Item not found' });
db.prepare('UPDATE bucket_list SET name = COALESCE(?, name), notes = COALESCE(?, notes) WHERE id = ?').run(name?.trim() || null, notes ?? null, req.params.id);
db.prepare(`UPDATE bucket_list SET
name = COALESCE(?, name),
notes = CASE WHEN ? THEN ? ELSE notes END,
lat = CASE WHEN ? THEN ? ELSE lat END,
lng = CASE WHEN ? THEN ? ELSE lng END,
country_code = CASE WHEN ? THEN ? ELSE country_code END,
target_date = CASE WHEN ? THEN ? ELSE target_date END
WHERE id = ?`).run(
name?.trim() || null,
notes !== undefined ? 1 : 0, notes !== undefined ? (notes || null) : null,
lat !== undefined ? 1 : 0, lat !== undefined ? (lat || null) : null,
lng !== undefined ? 1 : 0, lng !== undefined ? (lng || null) : null,
country_code !== undefined ? 1 : 0, country_code !== undefined ? (country_code || null) : null,
target_date !== undefined ? 1 : 0, target_date !== undefined ? (target_date || null) : null,
req.params.id
);
res.json({ item: db.prepare('SELECT * FROM bucket_list WHERE id = ?').get(req.params.id) });
});