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:
@@ -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) {
|
||||
|
||||
@@ -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) });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user