mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 06:11:45 +00:00
fix(journey): fix issue #704 — active logic, archive, places rename, search, trip reminders
- Derive journey lifecycle from linked trip dates (live/upcoming/completed/draft) instead of relying solely on status field; status=archived always wins - Add Archive/Restore Journey action in journey settings dialog - Rename cities → places end-to-end (SQL alias, TS types, stats field, all locales) - Wire up search icon: toggles inline input, filters by title+subtitle client-side - Fix channelConfigured check: trip reminders enabled by default since inapp is always available; remove channel check, controlled solely by admin setting - Expose notify_trip_reminder toggle in Admin → Settings → Notifications - Add trip_date_min/trip_date_max to listJourneys SQL for client-side lifecycle - Add archived status to Journey type (server + client) - Update all 15 locale files with new keys (search, archive, places, trip reminders)
This commit is contained in:
@@ -31,6 +31,7 @@ const ADMIN_SETTINGS_KEYS = [
|
||||
'allow_registration', 'allowed_file_types', 'require_mfa',
|
||||
'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', 'smtp_from', 'smtp_skip_tls_verify',
|
||||
'notification_channels', 'admin_webhook_url', 'admin_ntfy_server', 'admin_ntfy_topic', 'admin_ntfy_token',
|
||||
'notify_trip_reminder',
|
||||
'password_login', 'password_registration', 'oidc_login', 'oidc_registration',
|
||||
];
|
||||
|
||||
@@ -227,8 +228,7 @@ export function getAppConfig(authenticatedUser: { id: number } | null) {
|
||||
const notifChannelsRaw = (db.prepare("SELECT value FROM app_settings WHERE key = 'notification_channels'").get() as { value: string } | undefined)?.value || notifChannel;
|
||||
const activeChannels = notifChannelsRaw === 'none' ? [] : notifChannelsRaw.split(',').map((c: string) => c.trim()).filter(Boolean);
|
||||
const hasWebhookEnabled = activeChannels.includes('webhook');
|
||||
const channelConfigured = (activeChannels.includes('email') && hasSmtpHost) || hasWebhookEnabled;
|
||||
const tripRemindersEnabled = channelConfigured && tripReminderSetting !== 'false';
|
||||
const tripRemindersEnabled = tripReminderSetting !== 'false';
|
||||
const setupComplete = userCount > 0 && !(db.prepare("SELECT id FROM users WHERE role = 'admin' AND must_change_password = 1 LIMIT 1").get());
|
||||
|
||||
return {
|
||||
|
||||
@@ -59,12 +59,14 @@ export function listJourneys(userId: number) {
|
||||
SELECT DISTINCT j.*,
|
||||
(SELECT COUNT(*) FROM journey_entries je WHERE je.journey_id = j.id AND je.type != 'skeleton') as entry_count,
|
||||
(SELECT COUNT(*) FROM journey_photos jp JOIN journey_entries je2 ON jp.entry_id = je2.id WHERE je2.journey_id = j.id) as photo_count,
|
||||
(SELECT COUNT(DISTINCT je3.location_name) FROM journey_entries je3 WHERE je3.journey_id = j.id AND je3.location_name IS NOT NULL AND je3.location_name != '') as city_count
|
||||
(SELECT COUNT(DISTINCT je3.location_name) FROM journey_entries je3 WHERE je3.journey_id = j.id AND je3.location_name IS NOT NULL AND je3.location_name != '') as place_count,
|
||||
(SELECT MIN(t.start_date) FROM journey_trips jt JOIN trips t ON jt.trip_id = t.id WHERE jt.journey_id = j.id) as trip_date_min,
|
||||
(SELECT MAX(t.end_date) FROM journey_trips jt JOIN trips t ON jt.trip_id = t.id WHERE jt.journey_id = j.id) as trip_date_max
|
||||
FROM journeys j
|
||||
LEFT JOIN journey_contributors jc ON j.id = jc.journey_id AND jc.user_id = ?
|
||||
WHERE j.user_id = ? OR jc.user_id = ?
|
||||
ORDER BY j.updated_at DESC
|
||||
`).all(userId, userId, userId) as (Journey & { entry_count: number; photo_count: number; city_count: number })[];
|
||||
`).all(userId, userId, userId) as (Journey & { entry_count: number; photo_count: number; place_count: number; trip_date_min: string | null; trip_date_max: string | null })[];
|
||||
}
|
||||
|
||||
export function createJourney(userId: number, data: {
|
||||
@@ -159,7 +161,7 @@ export function getJourneyFull(journeyId: number, userId: number) {
|
||||
// stats
|
||||
const entryCount = entries.filter(e => e.type === 'entry').length;
|
||||
const photoCount = photos.length;
|
||||
const cities = [...new Set(entries.map(e => e.location_name).filter(Boolean))];
|
||||
const places = [...new Set(entries.map(e => e.location_name).filter(Boolean))];
|
||||
|
||||
const userPrefs = db.prepare(
|
||||
'SELECT hide_skeletons FROM journey_contributors WHERE journey_id = ? AND user_id = ?'
|
||||
@@ -170,7 +172,7 @@ export function getJourneyFull(journeyId: number, userId: number) {
|
||||
entries: enrichedEntries,
|
||||
trips,
|
||||
contributors,
|
||||
stats: { entries: entryCount, photos: photoCount, cities: cities.length },
|
||||
stats: { entries: entryCount, photos: photoCount, places: places.length },
|
||||
hide_skeletons: !!(userPrefs?.hide_skeletons),
|
||||
};
|
||||
}
|
||||
@@ -184,11 +186,13 @@ export function updateJourney(journeyId: number, userId: number, data: Partial<{
|
||||
}>): Journey | null {
|
||||
if (!canEdit(journeyId, userId)) return null;
|
||||
|
||||
const ALLOWED_STATUSES = ['draft', 'active', 'completed', 'archived'];
|
||||
const allowed = ['title', 'subtitle', 'cover_gradient', 'cover_image', 'status'];
|
||||
const fields: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
for (const [key, val] of Object.entries(data)) {
|
||||
if (val !== undefined && allowed.includes(key)) {
|
||||
if (key === 'status' && !ALLOWED_STATUSES.includes(val as string)) continue;
|
||||
fields.push(`${key} = ?`);
|
||||
values.push(val);
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ export function getPublicJourney(token: string) {
|
||||
const stats = {
|
||||
entries: entries.length,
|
||||
photos: photos.length,
|
||||
cities: new Set(entries.filter(e => e.location_name).map(e => e.location_name)).size,
|
||||
places: new Set(entries.filter(e => e.location_name).map(e => e.location_name)).size,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user