Bug Fixes + Speed Improvements (#871)

* Show timezone information on visits list (#865)

* Initial plan

* Initial investigation: Show timezone on visits list - planning implementation

Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

* Show timezone on visits list - add timezone badge display

Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: seanmorley15 <98704938+seanmorley15@users.noreply.github.com>

* fix: add additional timezones (#852)

* [BUG] Transportation node does not accept complex links in link parameter
Fixes #856

* Squashed commit of the following:

commit 59d5128cc642d133b0c166fbaf2d41a88c237d92
Merge: 0f9d31f4 7b8961e0
Author: Hosted Weblate <hosted@weblate.org>
Date:   Fri Sep 19 04:22:36 2025 +0200

    Merge branch 'origin/development' into Weblate.

commit 7b8961e02430b9b6fab7b22a7a8c1f7b06ff950b
Author: Orhun <orhunavcu@gmail.com>
Date:   Fri Sep 19 00:30:25 2025 +0200

    Translated using Weblate (Turkish)

    Currently translated at 16.7% (160 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/tr/

commit a8134bdbda318d00175c984785d150e38a1e24bf
Author: Orhun <orhunavcu@gmail.com>
Date:   Thu Sep 18 22:52:56 2025 +0200

    Added translation using Weblate (Turkish)

commit ac8a8ee8c9fc55da2d4ded1c4beac04a1ea66bb8
Merge: 2527e345 3fca3872
Author: Hosted Weblate <hosted@weblate.org>
Date:   Thu Sep 18 14:11:58 2025 +0200

    Merge branch 'origin/development' into Weblate.

commit 3fca387272d52dfcb634751a74e4a4b4fcf7ac6b
Merge: 4907ba87 85d8b45c
Author: Hosted Weblate <hosted@weblate.org>
Date:   Thu Sep 18 05:13:16 2025 +0200

    Merge branch 'origin/development' into Weblate.

commit 85d8b45c4eff77bcc9d5708f7fd8a4fcea91890b
Merge: 9f5cc9cc ae07c440
Author: Sean Morley <mail@seanmorley.com>
Date:   Wed Sep 17 19:25:35 2025 -0400

    Merge branch 'development' of github.com:seanmorley15/AdventureLog into development

commit 9f5cc9ccb8809cd5ae0c01e979dae271e9197987
Author: Sean Morley <mail@seanmorley.com>
Date:   Wed Sep 17 19:23:47 2025 -0400

    Remove duplicate comment for syncing development branch with main in .env.example

commit 77c1f516266b2db5e1c97c5cbc8a3fb9e08d40cd
Author: Sean Morley <mail@seanmorley.com>
Date:   Wed Sep 17 19:22:36 2025 -0400

    Squashed commit of the following:

    commit 9d4f1b8f534a3cdfb22812f2a25ababd7a236a87
    Author: Jacob <jacob.aulin@proton.me>
    Date:   Sat Sep 13 15:17:22 2025 +0200

        Translated using Weblate (Swedish)

        Currently translated at 99.8% (957 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sv/

    commit 8fac40cfde425c989521c891b3ba9c75ab32e57e
    Author: Christian S <schuld.christian@gmail.com>
    Date:   Sat Sep 13 12:54:52 2025 +0200

        Translated using Weblate (German)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

    commit 8e8c42396ec77b763983155e8b1e89cabf38ce17
    Author: Patricio Carrau <duckycb@proton.me>
    Date:   Tue Sep 9 21:59:48 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit be818ab408d00c5c26dfb3b25632604a415d3570
    Author: pplulee <hi@pplulee.me>
    Date:   Mon Sep 8 04:06:54 2025 +0100

        fix(i18n): update Chinese translations for location-related terms (#829)

        Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    commit 9e40dcf6a1dc194d4694a114b3c7e88135121016
    Merge: af2f2809 733eefce
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sun Sep 7 23:03:57 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit af2f28090b9242fb7ab263fa5bbb95a5bcc1b27f
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sun Sep 7 23:00:33 2025 -0400

        [BUG] Location Visit End Date not affected by Location Timezone
        Fixes #843

    commit 733eefceddbdad01726364e5d4523605f095fde2
    Author: Alex <div@alexe.at>
    Date:   Sun Sep 7 23:28:20 2025 +0200

        Translated using Weblate (German)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

    commit 6c750d1c8f95b42418893e15ad46c3d4ed86d053
    Author: fantastron27 <fantastron27@gmail.com>
    Date:   Sun Sep 7 09:17:16 2025 +0200

        Translated using Weblate (Slovak)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sk/

    commit f733b3b96bbddc71d426f2e60320a5ad2f6755af
    Merge: 769ea6ad af4e541c
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:36:35 2025 -0400

        Merge branch 'development' of github.com:seanmorley15/AdventureLog into development

    commit 769ea6ad710890e931aabace2c00dc37436f869f
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:36:33 2025 -0400

        Implement code changes to enhance functionality and improve performance

    commit af4e541c1c9e7309857102287199279aec339387
    Author: fantastron27 <fantastron27@gmail.com>
    Date:   Sun Sep 7 03:36:23 2025 +0200

        Added Slovak translations (#815)

        * Created sk.json

        * Update Navbar.svelte

        * Update +layout.svelte

        ---------

        Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    commit 904474d757577229b47441d1378a6fd6788fbe40
    Merge: d4709434 f87a5fe3
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:31:58 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit d47094346c0b63ea753294a0786414e5e070ae7f
    Merge: 4a5f59bf 6366a3eb
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:29:39 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit f87a5fe3bcc2fe28cfc206fb5cba517bbffa8df6
    Author: Sergio <garcia.sergio@me.com>
    Date:   Sun Sep 7 01:12:50 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit 4a5f59bfd24e32fdf3558b009a8f636636cb3663
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:06:17 2025 -0400

        Fixes #654

    commit c1302bb54ab272c2a98c53ce0d508b7d39e9674b
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 20:52:05 2025 -0400

        [BUG] Single day Collections will think location visits are out of date range
        Fixes #827

    commit 773f2d65bbfb2a9591b31fabfd6844612b840f1a
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 19:52:28 2025 -0400

        [BUG] Server Error (500) when trying to access the API docs
        Fixes #712

    commit 4228db249ed5e3261931a1cdb3895d0ddd3ac4ac
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 19:44:00 2025 -0400

        [BUG]Ordered Itinerary includes visits that are outside itinerary date range
        Fixes #746

    commit 26f36cabb0a860f10d7ba62b5279ddd1e282c78e
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 18:36:50 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit 3bfd2dd5615afdbd04e3451c2ef728f1d7caf466
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 12:33:23 2025 -0400

        Remove empty English (United States) locale file

    commit 6366a3eba6ab72090e52be474212a663799dfe19
    Author: Nikolai Eidsheim <nikolai.eidsheim@gmail.com>
    Date:   Sat Sep 6 18:10:15 2025 +0200

        Translated using Weblate (Norwegian Bokmål)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/nb_NO/

    commit 671cd3701fc5a601f2f1bad9aef93106f91eec0b
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 16:58:04 2025 +0200

        Added translation using Weblate (English (United States))

    commit bdbbe5f4978f041f620f0503da69fa870cb1997c
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 16:54:43 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

commit f310771702291be93e1c6b3bdb5a76e02fb8cae8
Author: Sean Morley <98704938+seanmorley15@users.noreply.github.com>
Date:   Mon Sep 8 22:38:08 2025 -0400

    Update issue templates for bug reports, deployment issues, and feature requests (#849)

commit 02ed89fa46fa22f8c5d96b0e7f0948204dec9306
Author: Sean Morley <98704938+seanmorley15@users.noreply.github.com>
Date:   Sun Sep 7 23:16:12 2025 -0400

    Timezone fixes, Translations, and Misc Fixes (#844)

    * Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    * Added translation using Weblate (English (United States))

    * Translated using Weblate (Norwegian Bokmål)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/nb_NO/

    * Remove empty English (United States) locale file

    * Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    * [BUG]Ordered Itinerary includes visits that are outside itinerary date range
    Fixes #746

    * [BUG] Server Error (500) when trying to access the API docs
    Fixes #712

    * [BUG] Single day Collections will think location visits are out of date range
    Fixes #827

    * Fixes #654

    * Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    * Added Slovak translations (#815)

    * Created sk.json

    * Update Navbar.svelte

    * Update +layout.svelte

    ---------

    Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    * Implement code changes to enhance functionality and improve performance

    * Translated using Weblate (Slovak)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sk/

    * Translated using Weblate (German)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

    * [BUG] Location Visit End Date not affected by Location Timezone
    Fixes #843

    * fix(i18n): update Chinese translations for location-related terms (#829)

    Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    ---------

    Co-authored-by: Nikolai Eidsheim <nikolai.eidsheim@gmail.com>
    Co-authored-by: Sergio <garcia.sergio@me.com>
    Co-authored-by: fantastron27 <fantastron27@gmail.com>
    Co-authored-by: Alex <div@alexe.at>
    Co-authored-by: pplulee <hi@pplulee.me>

commit ae07c44030ff597be80988b63460595e8bea132f
Author: Sean Morley <mail@seanmorley.com>
Date:   Wed Sep 17 19:23:47 2025 -0400

    Remove duplicate comment for syncing development branch with main in .env.example

commit 94964f1fb16583f1ff63ddf4aee2469a532b0c6c
Author: Sean Morley <mail@seanmorley.com>
Date:   Wed Sep 17 19:22:36 2025 -0400

    Squashed commit of the following:

    commit 9d4f1b8f534a3cdfb22812f2a25ababd7a236a87
    Author: Jacob <jacob.aulin@proton.me>
    Date:   Sat Sep 13 15:17:22 2025 +0200

        Translated using Weblate (Swedish)

        Currently translated at 99.8% (957 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sv/

    commit 8fac40cfde425c989521c891b3ba9c75ab32e57e
    Author: Christian S <schuld.christian@gmail.com>
    Date:   Sat Sep 13 12:54:52 2025 +0200

        Translated using Weblate (German)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

    commit 8e8c42396ec77b763983155e8b1e89cabf38ce17
    Author: Patricio Carrau <duckycb@proton.me>
    Date:   Tue Sep 9 21:59:48 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit be818ab408d00c5c26dfb3b25632604a415d3570
    Author: pplulee <hi@pplulee.me>
    Date:   Mon Sep 8 04:06:54 2025 +0100

        fix(i18n): update Chinese translations for location-related terms (#829)

        Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    commit 9e40dcf6a1dc194d4694a114b3c7e88135121016
    Merge: af2f2809 733eefce
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sun Sep 7 23:03:57 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit af2f28090b9242fb7ab263fa5bbb95a5bcc1b27f
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sun Sep 7 23:00:33 2025 -0400

        [BUG] Location Visit End Date not affected by Location Timezone
        Fixes #843

    commit 733eefceddbdad01726364e5d4523605f095fde2
    Author: Alex <div@alexe.at>
    Date:   Sun Sep 7 23:28:20 2025 +0200

        Translated using Weblate (German)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

    commit 6c750d1c8f95b42418893e15ad46c3d4ed86d053
    Author: fantastron27 <fantastron27@gmail.com>
    Date:   Sun Sep 7 09:17:16 2025 +0200

        Translated using Weblate (Slovak)

        Currently translated at 100.0% (958 of 958 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sk/

    commit f733b3b96bbddc71d426f2e60320a5ad2f6755af
    Merge: 769ea6ad af4e541c
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:36:35 2025 -0400

        Merge branch 'development' of github.com:seanmorley15/AdventureLog into development

    commit 769ea6ad710890e931aabace2c00dc37436f869f
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:36:33 2025 -0400

        Implement code changes to enhance functionality and improve performance

    commit af4e541c1c9e7309857102287199279aec339387
    Author: fantastron27 <fantastron27@gmail.com>
    Date:   Sun Sep 7 03:36:23 2025 +0200

        Added Slovak translations (#815)

        * Created sk.json

        * Update Navbar.svelte

        * Update +layout.svelte

        ---------

        Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

    commit 904474d757577229b47441d1378a6fd6788fbe40
    Merge: d4709434 f87a5fe3
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:31:58 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit d47094346c0b63ea753294a0786414e5e070ae7f
    Merge: 4a5f59bf 6366a3eb
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:29:39 2025 -0400

        Merge remote-tracking branch 'weblate/development' into development

    commit f87a5fe3bcc2fe28cfc206fb5cba517bbffa8df6
    Author: Sergio <garcia.sergio@me.com>
    Date:   Sun Sep 7 01:12:50 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit 4a5f59bfd24e32fdf3558b009a8f636636cb3663
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 21:06:17 2025 -0400

        Fixes #654

    commit c1302bb54ab272c2a98c53ce0d508b7d39e9674b
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 20:52:05 2025 -0400

        [BUG] Single day Collections will think location visits are out of date range
        Fixes #827

    commit 773f2d65bbfb2a9591b31fabfd6844612b840f1a
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 19:52:28 2025 -0400

        [BUG] Server Error (500) when trying to access the API docs
        Fixes #712

    commit 4228db249ed5e3261931a1cdb3895d0ddd3ac4ac
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 19:44:00 2025 -0400

        [BUG]Ordered Itinerary includes visits that are outside itinerary date range
        Fixes #746

    commit 26f36cabb0a860f10d7ba62b5279ddd1e282c78e
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 18:36:50 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

    commit 3bfd2dd5615afdbd04e3451c2ef728f1d7caf466
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 12:33:23 2025 -0400

        Remove empty English (United States) locale file

    commit 6366a3eba6ab72090e52be474212a663799dfe19
    Author: Nikolai Eidsheim <nikolai.eidsheim@gmail.com>
    Date:   Sat Sep 6 18:10:15 2025 +0200

        Translated using Weblate (Norwegian Bokmål)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/nb_NO/

    commit 671cd3701fc5a601f2f1bad9aef93106f91eec0b
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 16:58:04 2025 +0200

        Added translation using Weblate (English (United States))

    commit bdbbe5f4978f041f620f0503da69fa870cb1997c
    Author: Sean Morley <mail@seanmorley.com>
    Date:   Sat Sep 6 16:54:43 2025 +0200

        Translated using Weblate (Spanish)

        Currently translated at 100.0% (956 of 956 strings)

        Translation: AdventureLog/Web App
        Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

commit 9d4f1b8f534a3cdfb22812f2a25ababd7a236a87
Author: Jacob <jacob.aulin@proton.me>
Date:   Sat Sep 13 15:17:22 2025 +0200

    Translated using Weblate (Swedish)

    Currently translated at 99.8% (957 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sv/

commit 8fac40cfde425c989521c891b3ba9c75ab32e57e
Author: Christian S <schuld.christian@gmail.com>
Date:   Sat Sep 13 12:54:52 2025 +0200

    Translated using Weblate (German)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

commit 8e8c42396ec77b763983155e8b1e89cabf38ce17
Author: Patricio Carrau <duckycb@proton.me>
Date:   Tue Sep 9 21:59:48 2025 +0200

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

commit be818ab408d00c5c26dfb3b25632604a415d3570
Author: pplulee <hi@pplulee.me>
Date:   Mon Sep 8 04:06:54 2025 +0100

    fix(i18n): update Chinese translations for location-related terms (#829)

    Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

commit 9e40dcf6a1dc194d4694a114b3c7e88135121016
Merge: af2f2809 733eefce
Author: Sean Morley <mail@seanmorley.com>
Date:   Sun Sep 7 23:03:57 2025 -0400

    Merge remote-tracking branch 'weblate/development' into development

commit af2f28090b9242fb7ab263fa5bbb95a5bcc1b27f
Author: Sean Morley <mail@seanmorley.com>
Date:   Sun Sep 7 23:00:33 2025 -0400

    [BUG] Location Visit End Date not affected by Location Timezone
    Fixes #843

commit 733eefceddbdad01726364e5d4523605f095fde2
Author: Alex <div@alexe.at>
Date:   Sun Sep 7 23:28:20 2025 +0200

    Translated using Weblate (German)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/de/

commit 6c750d1c8f95b42418893e15ad46c3d4ed86d053
Author: fantastron27 <fantastron27@gmail.com>
Date:   Sun Sep 7 09:17:16 2025 +0200

    Translated using Weblate (Slovak)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/sk/

commit f733b3b96bbddc71d426f2e60320a5ad2f6755af
Merge: 769ea6ad af4e541c
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 21:36:35 2025 -0400

    Merge branch 'development' of github.com:seanmorley15/AdventureLog into development

commit 769ea6ad710890e931aabace2c00dc37436f869f
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 21:36:33 2025 -0400

    Implement code changes to enhance functionality and improve performance

commit af4e541c1c9e7309857102287199279aec339387
Author: fantastron27 <fantastron27@gmail.com>
Date:   Sun Sep 7 03:36:23 2025 +0200

    Added Slovak translations (#815)

    * Created sk.json

    * Update Navbar.svelte

    * Update +layout.svelte

    ---------

    Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

commit 904474d757577229b47441d1378a6fd6788fbe40
Merge: d4709434 f87a5fe3
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 21:31:58 2025 -0400

    Merge remote-tracking branch 'weblate/development' into development

commit d47094346c0b63ea753294a0786414e5e070ae7f
Merge: 4a5f59bf 6366a3eb
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 21:29:39 2025 -0400

    Merge remote-tracking branch 'weblate/development' into development

commit f87a5fe3bcc2fe28cfc206fb5cba517bbffa8df6
Author: Sergio <garcia.sergio@me.com>
Date:   Sun Sep 7 01:12:50 2025 +0200

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

commit 4a5f59bfd24e32fdf3558b009a8f636636cb3663
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 21:06:17 2025 -0400

    Fixes #654

commit c1302bb54ab272c2a98c53ce0d508b7d39e9674b
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 20:52:05 2025 -0400

    [BUG] Single day Collections will think location visits are out of date range
    Fixes #827

commit 773f2d65bbfb2a9591b31fabfd6844612b840f1a
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 19:52:28 2025 -0400

    [BUG] Server Error (500) when trying to access the API docs
    Fixes #712

commit 4228db249ed5e3261931a1cdb3895d0ddd3ac4ac
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 19:44:00 2025 -0400

    [BUG]Ordered Itinerary includes visits that are outside itinerary date range
    Fixes #746

commit 26f36cabb0a860f10d7ba62b5279ddd1e282c78e
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 18:36:50 2025 +0200

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

commit 3bfd2dd5615afdbd04e3451c2ef728f1d7caf466
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 12:33:23 2025 -0400

    Remove empty English (United States) locale file

commit 6366a3eba6ab72090e52be474212a663799dfe19
Author: Nikolai Eidsheim <nikolai.eidsheim@gmail.com>
Date:   Sat Sep 6 18:10:15 2025 +0200

    Translated using Weblate (Norwegian Bokmål)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/nb_NO/

commit 671cd3701fc5a601f2f1bad9aef93106f91eec0b
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 16:58:04 2025 +0200

    Added translation using Weblate (English (United States))

commit bdbbe5f4978f041f620f0503da69fa870cb1997c
Author: Sean Morley <mail@seanmorley.com>
Date:   Sat Sep 6 16:54:43 2025 +0200

    Translated using Weblate (Spanish)

    Currently translated at 100.0% (956 of 956 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/es/

* Translated using Weblate (French)

Currently translated at 100.0% (958 of 958 strings)

Translation: AdventureLog/Web App
Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/fr/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (958 of 958 strings)

Translation: AdventureLog/Web App
Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/tr/

* feat: enhance serializers and views for nested representations and lightweight data fetching

* feat: implement caching for visits by country and region endpoints

* feat: update LocationSerializer to include debug logging and adjust nested context handling in CollectionSerializer

* feat: enhance CollectionSerializer and CollectionViewSet to support nested context handling

* feat(worldtravel): add globe spin feature with loading and result display

- Introduced a toggle button for the globe spin feature.
- Implemented fetch logic for globe spin data from the API.
- Added loading state with a spinning globe animation.
- Displayed country information, including flag, subregion, and visit statistics.
- Enhanced UI with animations for loading and displaying results.
- Included error handling for API fetch failures.

* Add Turkish language support to the application

* feat(map): enhance pin styling with gradient backgrounds and improved hover effects

* Squashed commit of the following:

commit 990b0645059421c4a293fbf64830a6d864ceb40e
Author: Henrique Fonseca Veloso <henriquefv@tutamail.com>
Date:   Sun Sep 21 00:27:59 2025 +0200

    Translated using Weblate (Portuguese (Brazil))

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/pt_BR/

commit 03a4b9235faa849fb817348a2774e834a6851dc3
Author: Orhun <orhunavcu@gmail.com>
Date:   Fri Sep 19 19:38:12 2025 +0200

    Translated using Weblate (Turkish)

    Currently translated at 100.0% (958 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/tr/

commit 1ccdc678627eb5915b56a8bfb3465928c80a524f
Author: Henrique Fonseca Veloso <henriquefv@tutamail.com>
Date:   Sat Sep 20 20:01:36 2025 +0200

    Translated using Weblate (Portuguese (Brazil))

    Currently translated at 97.0% (930 of 958 strings)

    Translation: AdventureLog/Web App
    Translate-URL: https://hosted.weblate.org/projects/adventurelog/web-app/pt_BR/

* fix(config): update appVersion to v0.11.0-main-09212025

* fix duration calculation on ordered collection view (#867)

Co-authored-by: Sean Morley <98704938+seanmorley15@users.noreply.github.com>

* Update frontend/src/routes/worldtravel/+page.svelte

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update backend/server/worldtravel/views.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update frontend/src/routes/map/+page.svelte

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Rohan <Alchez@users.noreply.github.com>
Co-authored-by: Orhun <orhunavcu@gmail.com>
Co-authored-by: vorbeiei <vorbeiei@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Sean Morley
2025-09-21 22:06:24 -04:00
committed by GitHub
parent 4907ba8700
commit 240c617010
43 changed files with 13002 additions and 5398 deletions

5551
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

View File

@@ -43,8 +43,8 @@
try {
// Fetch both own collections and shared collections
const [ownRes, sharedRes] = await Promise.all([
fetch(`/api/collections/all/`, { method: 'GET' }),
fetch(`/api/collections/shared`, { method: 'GET' })
fetch(`/api/collections/all?nested=true`, { method: 'GET' }),
fetch(`/api/collections/shared?nested=true`, { method: 'GET' })
]);
const ownResult = await ownRes.json();

View File

@@ -52,10 +52,12 @@
let outsideCollectionRange: boolean = false;
$: {
if (collection) {
if (collection && collection.start_date && collection.end_date) {
outsideCollectionRange = adventure.visits.every((visit) =>
isEntityOutsideCollectionDateRange(visit, collection)
);
} else {
outsideCollectionRange = false;
}
}

View File

@@ -64,7 +64,8 @@
ja: '日本語',
ar: 'العربية',
'pt-br': 'Português (Brasil)',
'sk': 'Slovenský'
sk: 'Slovenský',
tr: 'Türkçe'
};
const submitLocaleChange = (event: Event) => {

View File

@@ -899,6 +899,9 @@
{:else}
<ClockIcon class="w-3 h-3 text-base-content/50" />
{/if}
{#if visit.timezone && !isAllDay(visit.start_date)}
<span class="badge badge-outline badge-sm">{visit.timezone}</span>
{/if}
<div class="text-sm font-medium truncate">
{#if isAllDay(visit.start_date)}
{visit.start_date && typeof visit.start_date === 'string'

View File

@@ -1,4 +1,4 @@
export let appVersion = 'v0.11.0-main-09172025';
export let appVersion = 'v0.11.0-main-09212025';
export let versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.11.0';
export let appTitle = 'AdventureLog';
export let copyrightYear = '2023-2025';

View File

@@ -465,3 +465,12 @@ export type WandererTrail = {
updated: string; // ISO 8601 date string
waypoints: string[];
};
export type Pin = {
id: string;
name: string;
latitude: string;
longitude: string;
is_visited?: boolean;
category: Category | null;
};

View File

@@ -902,7 +902,13 @@
"shared": "مشترك",
"shared_with": "مشترك مع",
"unshared": "غير مشترك",
"with": "مع"
"with": "مع",
"available_users": "المستخدمين المتاحين",
"invite_failed": "فشل دعوة",
"invite_revoked": "دعوة إلغاء",
"invite_sent": "دعوة إرسال",
"revoke_failed": "فشل الإلغاء",
"unshare_failed": "فشل Unshare"
},
"strava": {
"account_connected": "حساب متصل",
@@ -1013,6 +1019,21 @@
"visit_remove_failed": "فشل في إزالة الزيارة",
"visit_to": "زيارة",
"visited_first": "زار أولاً",
"getting_location_details": "الحصول على تفاصيل الموقع"
"getting_location_details": "الحصول على تفاصيل الموقع",
"cities_available": "المدن المتاحة",
"destination_revealed": "كشفت الوجهة!",
"dive_deeper": "الغوص أعمق",
"exploration_progress": "تقدم الاستكشاف",
"explore_country": "استكشف البلد",
"globe_spin_error_desc": "خطأ جلب بيانات الدوران العالمي",
"hide_globe_spin": "إخفاء الدوران العالمي",
"in": "في",
"loading_globe_spin": "تحميل الكرة الأرضية",
"no_globe_spin_data": "لا توجد بيانات تدور حول العالم",
"show_globe_spin": "عرض Globe Spin",
"spin_again": "تدور مرة أخرى",
"spinning_globe": "كرة الغزل",
"try_again": "حاول ثانية",
"your_random_adventure_awaits": "مغامرتك العشوائية تنتظر!"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -535,7 +535,22 @@
"oldest_first": "Oldest First",
"visited_first": "Visited First",
"unvisited_first": "Unvisited First",
"getting_location_details": "Getting location details"
"getting_location_details": "Getting location details",
"hide_globe_spin": "Hide Globe Spin",
"show_globe_spin": "Show Globe Spin",
"loading_globe_spin": "Loading Globe Spin",
"spinning_globe": "Spinning Globe",
"destination_revealed": "Destination Revealed!",
"your_random_adventure_awaits": "Your Random Adventure Awaits!",
"exploration_progress": "Exploration Progress",
"dive_deeper": "Dive Deeper",
"cities_available": "Cities Available",
"in": "in",
"explore_country": "Explore Country",
"spin_again": "Spin Again",
"globe_spin_error_desc": "Error fetching globe spin data",
"try_again": "Try Again",
"no_globe_spin_data": "No Globe Spin Data"
},
"auth": {
"username": "Username",
@@ -874,7 +889,13 @@
"revoke_invite": "Revoke Invite",
"send_invite": "Send Invite",
"available": "Available",
"pending": "Pending"
"pending": "Pending",
"available_users": "Available Users",
"revoke_failed": "Revoke Failed",
"invite_revoked": "Invite Revoked",
"unshare_failed": "Unshare Failed",
"invite_failed": "Invite Failed",
"invite_sent": "Invite Sent"
},
"languages": {},
"profile": {

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,6 @@
"view_license": "Afficher la licence"
},
"adventures": {
"activities": {},
"add_to_collection": "Ajouter à la collection",
"delete": "Supprimer",
"edit_adventure": "Modifier l'aventure",
@@ -189,7 +188,7 @@
"display_name": "Nom d'affichage",
"location_details": "Détails du lieu",
"lodging": "Hébergement",
"lodging_delete_confirm": "Êtes-vous sûr de vouloir supprimer cet hébergement? \nCette action ne peut pas être annulée.",
"lodging_delete_confirm": "Êtes-vous sûr de vouloir supprimer cet hébergement? Cette action ne peut pas être annulée.",
"lodging_information": "Informations sur l'hébergement",
"price": "Prix",
"region": "Région",
@@ -244,7 +243,7 @@
"edit_location": "Modifier l'emplacement",
"location_create_error": "Échec de la création de l'emplacement",
"location_created": "Emplacement créé",
"location_delete_confirm": "Êtes-vous sûr de vouloir supprimer cet emplacement? \nCette action ne peut pas être annulée.",
"location_delete_confirm": "Êtes-vous sûr de vouloir supprimer cet emplacement? Cette action ne peut pas être annulée.",
"location_delete_success": "Emplacement supprimé avec succès!",
"location_not_found": "Emplacement introuvable",
"location_not_found_desc": "L'emplacement que vous recherchiez n'a pas pu être trouvé. \nVeuillez essayer un autre emplacement ou revenir plus tard.",
@@ -564,7 +563,22 @@
"unvisited_first": "Sans visité d'abord",
"visited_first": "Visité en premier",
"total_items": "Total des articles",
"getting_location_details": "Obtenir les détails de l'emplacement"
"getting_location_details": "Obtenir les détails de l'emplacement",
"cities_available": "Villes disponibles",
"destination_revealed": "Destination révélée!",
"dive_deeper": "Plonger plus profondément",
"exploration_progress": "Progrès de l'exploration",
"explore_country": "Explorer le pays",
"globe_spin_error_desc": "Erreur pour récupérer les données de spin globe",
"hide_globe_spin": "Hide Globe Spin",
"in": "dans",
"loading_globe_spin": "Chargement du globe Spin",
"no_globe_spin_data": "Pas de données de spin globe",
"show_globe_spin": "Montrer le spin au globe",
"spin_again": "Remonter",
"spinning_globe": "Globe de rotation",
"try_again": "Essayer à nouveau",
"your_random_adventure_awaits": "Votre aventure aléatoire vous attend!"
},
"settings": {
"account_settings": "Paramètres du compte utilisateur",
@@ -838,7 +852,6 @@
"show_activities": "Montrer les activités",
"show_visited_cities": "Villes visites"
},
"languages": {},
"share": {
"no_users_shared": "Aucun utilisateur",
"not_shared_with": "Pas encore partagé avec",
@@ -853,7 +866,13 @@
"available": "Disponible",
"pending": "En attente",
"revoke_invite": "Revoke Inviter",
"send_invite": "Envoyer l'invitation"
"send_invite": "Envoyer l'invitation",
"available_users": "Utilisateurs disponibles",
"invite_failed": "L'invitation a échoué",
"invite_revoked": "Inviter révoqué",
"invite_sent": "Inviter envoyé",
"revoke_failed": "Revoke a échoué",
"unshare_failed": "Sans partage a échoué"
},
"profile": {
"member_since": "Membre depuis",

View File

@@ -564,7 +564,22 @@
"unvisited_first": "Non visitato per primo",
"visited_first": "Visitato per primo",
"total_items": "Articoli totali",
"getting_location_details": "Ottenere dettagli sulla posizione"
"getting_location_details": "Ottenere dettagli sulla posizione",
"cities_available": "Città disponibili",
"destination_revealed": "Destinazione rivelata!",
"dive_deeper": "Immergersi più in profondità",
"exploration_progress": "Progressi di esplorazione",
"explore_country": "Esplora il paese",
"globe_spin_error_desc": "Errore che recupera i dati di spin Globe",
"hide_globe_spin": "Nascondi lo spin di globo",
"in": "In",
"loading_globe_spin": "Caricamento di rotazione del globo",
"no_globe_spin_data": "Nessun dati di spin Globe",
"show_globe_spin": "Mostra lo spin globo",
"spin_again": "Girare di nuovo",
"spinning_globe": "Globe rotante",
"try_again": "Riprova",
"your_random_adventure_awaits": "La tua avventura casuale ti aspetta!"
},
"settings": {
"account_settings": "Impostazioni dell'account utente",
@@ -853,7 +868,13 @@
"available": "Disponibile",
"pending": "In attesa di",
"revoke_invite": "Revoca invito",
"send_invite": "Invia invito"
"send_invite": "Invia invito",
"available_users": "Utenti disponibili",
"invite_failed": "Invito fallito",
"invite_revoked": "Invita revocato",
"invite_sent": "Invito inviato",
"revoke_failed": "Revoca fallita",
"unshare_failed": "Unshare non è riuscito"
},
"profile": {
"member_since": "Membro da",

View File

@@ -902,7 +902,13 @@
"shared": "共有",
"shared_with": "共有",
"unshared": "非共有",
"with": "と"
"with": "と",
"available_users": "利用可能なユーザー",
"invite_failed": "招待は失敗しました",
"invite_revoked": "招待された招待",
"invite_sent": "送信招待",
"revoke_failed": "取り消しは失敗しました",
"unshare_failed": "UNSHAREは失敗しました"
},
"strava": {
"account_connected": "接続されたアカウント",
@@ -1013,6 +1019,21 @@
"visit_remove_failed": "訪問を削除できませんでした",
"visit_to": "訪問",
"visited_first": "最初に訪問しました",
"getting_location_details": "場所の詳細を取得します"
"getting_location_details": "場所の詳細を取得します",
"cities_available": "利用可能な都市",
"destination_revealed": "目的地が明らかに!",
"dive_deeper": "より深く潜ります",
"exploration_progress": "探索の進行",
"explore_country": "国を探索します",
"globe_spin_error_desc": "グローブスピンデータの取得エラー",
"hide_globe_spin": "グローブスピンを隠します",
"in": "で",
"loading_globe_spin": "グローブスピンのロード",
"no_globe_spin_data": "グローブスピンデータはありません",
"show_globe_spin": "グローブスピンを表示します",
"spin_again": "もう一度スピンします",
"spinning_globe": "スピニンググローブ",
"try_again": "もう一度やり直してください",
"your_random_adventure_awaits": "あなたのランダムな冒険が待っています!"
}
}

View File

@@ -846,7 +846,13 @@
"available": "사용 가능",
"pending": "보류 중",
"revoke_invite": "취소 초대",
"send_invite": "초대를 보내십시오"
"send_invite": "초대를 보내십시오",
"available_users": "사용 가능한 사용자",
"invite_failed": "초대 실패",
"invite_revoked": "취소 된 초대",
"invite_sent": "초대장",
"revoke_failed": "취소가 실패했습니다",
"unshare_failed": "공해를 실패했습니다"
},
"transportation": {
"edit": "편집",
@@ -938,7 +944,22 @@
"unvisited_first": "먼저 방문하지 않습니다",
"visited_first": "먼저 방문했습니다",
"total_items": "총 항목",
"getting_location_details": "위치 세부 정보 얻기"
"getting_location_details": "위치 세부 정보 얻기",
"dive_deeper": "더 깊이 다이빙하십시오",
"exploration_progress": "탐사 진행",
"explore_country": "국가를 탐험하십시오",
"globe_spin_error_desc": "오류 페치 글로브 스핀 데이터",
"hide_globe_spin": "글로브 스핀을 숨기십시오",
"in": "~에",
"loading_globe_spin": "로드 글로브 스핀",
"no_globe_spin_data": "글로브 스핀 데이터가 없습니다",
"show_globe_spin": "글로브 스핀을 보여주십시오",
"spin_again": "다시 회전하십시오",
"spinning_globe": "회전하는 글로브",
"try_again": "다시 시도하십시오",
"your_random_adventure_awaits": "당신의 임의의 모험이 기다리고 있습니다!",
"cities_available": "이용 가능",
"destination_revealed": "목적지 공개!"
},
"lodging": {
"apartment": "아파트",

View File

@@ -564,7 +564,22 @@
"unvisited_first": "Eerst niet bezocht",
"visited_first": "Eerst bezocht",
"total_items": "Totale items",
"getting_location_details": "Locatiegegevens krijgen"
"getting_location_details": "Locatiegegevens krijgen",
"cities_available": "Steden beschikbaar",
"destination_revealed": "Bestemming onthuld!",
"dive_deeper": "Duik dieper",
"exploration_progress": "Verkennings voortgang",
"explore_country": "Verken het land",
"globe_spin_error_desc": "Fout bij het ophalen van globe spin -gegevens",
"hide_globe_spin": "Globe spin verbergen",
"in": "in",
"loading_globe_spin": "Globe spin laden",
"no_globe_spin_data": "Geen Globe spin -gegevens",
"show_globe_spin": "Toon Globe Spin",
"spin_again": "Weer spinnen",
"spinning_globe": "Spinnende bol",
"try_again": "Probeer het opnieuw",
"your_random_adventure_awaits": "Je willekeurige avontuur wacht!"
},
"settings": {
"account_settings": "Gebruikersaccount instellingen",
@@ -853,7 +868,13 @@
"available": "Beschikbaar",
"pending": "In behandeling",
"revoke_invite": "Revoke uitnodigen",
"send_invite": "Stuur uitnodiging"
"send_invite": "Stuur uitnodiging",
"available_users": "Beschikbare gebruikers",
"invite_failed": "Uitnodigen mislukt",
"invite_revoked": "Uitnodigen ingetrokken",
"invite_sent": "Uitnodigen verzonden",
"revoke_failed": "Revoke mislukt",
"unshare_failed": "Onverschuiving mislukt"
},
"profile": {
"member_since": "Lid sinds",

View File

@@ -534,7 +534,22 @@
"unvisited_first": "Uvisitert først",
"visited_first": "Besøkte først",
"total_items": "Totalt gjenstander",
"getting_location_details": "Få stedsdetaljer"
"getting_location_details": "Få stedsdetaljer",
"cities_available": "Byer tilgjengelig",
"destination_revealed": "Destinasjon avslørt!",
"dive_deeper": "Dykk dypere",
"exploration_progress": "Utforskningsfremgang",
"explore_country": "Utforsk landet",
"globe_spin_error_desc": "Feilhåndtering av klode -spinndata",
"hide_globe_spin": "Skjul klode spinn",
"in": "i",
"loading_globe_spin": "Laster klode spinn",
"no_globe_spin_data": "Ingen klode spinndata",
"show_globe_spin": "Vis Globe Spin",
"spin_again": "Spinn igjen",
"spinning_globe": "Spinnende klode",
"try_again": "Prøv igjen",
"your_random_adventure_awaits": "Ditt tilfeldige eventyr venter!"
},
"auth": {
"username": "Brukernavn",
@@ -873,7 +888,13 @@
"available": "Tilgjengelig",
"pending": "I påvente av",
"revoke_invite": "Revoke Inviter",
"send_invite": "Send invitasjon"
"send_invite": "Send invitasjon",
"available_users": "Tilgjengelige brukere",
"invite_failed": "Inviter mislyktes",
"invite_revoked": "Inviter tilbakekalt",
"invite_sent": "Inviter sendt",
"revoke_failed": "Revoke mislyktes",
"unshare_failed": "Unshare mislyktes"
},
"profile": {
"member_since": "Medlem siden",

View File

@@ -535,7 +535,22 @@
"unvisited_first": "Najpierw niewidziane",
"visited_first": "Odwiedziłem pierwszy",
"total_items": "Całkowite przedmioty",
"getting_location_details": "Uzyskanie szczegółów lokalizacji"
"getting_location_details": "Uzyskanie szczegółów lokalizacji",
"cities_available": "Dostępne miasta",
"destination_revealed": "Ujawnione miejsce docelowe!",
"dive_deeper": "Nurkuj głębiej",
"exploration_progress": "Postęp eksploracyjny",
"explore_country": "Poznaj kraj",
"globe_spin_error_desc": "Błąd przyciąganie danych spinowych globe",
"hide_globe_spin": "Ukryj globe spin",
"in": "W",
"loading_globe_spin": "Ładowanie globowego spinu",
"no_globe_spin_data": "Brak danych spinowych globe",
"show_globe_spin": "Pokaż globe spin",
"spin_again": "Obrócić ponownie",
"spinning_globe": "Spinning Globe",
"try_again": "Spróbuj ponownie",
"your_random_adventure_awaits": "Twoja przypadkowa przygoda czeka!"
},
"auth": {
"username": "Nazwa użytkownika",
@@ -852,7 +867,13 @@
"available": "Dostępny",
"pending": "Aż do",
"revoke_invite": "Cofnij zaproszenie",
"send_invite": "Wyślij zaproszenie"
"send_invite": "Wyślij zaproszenie",
"available_users": "Dostępni użytkownicy",
"invite_failed": "Zaproś nieudane",
"invite_revoked": "Zaproś cofnięte",
"invite_sent": "Zaproś wysłane",
"revoke_failed": "Revoke nie powiodło się",
"unshare_failed": "Unhare nie powiodło się"
},
"languages": {},
"profile": {

File diff suppressed because it is too large Load Diff

View File

@@ -535,7 +535,22 @@
"unvisited_first": "Сначала не посещенные",
"visited_first": "Сначала посещенные",
"total_items": "Общие предметы",
"getting_location_details": "Получение деталей локации"
"getting_location_details": "Получение деталей локации",
"cities_available": "Города доступны",
"destination_revealed": "Открыто место!",
"dive_deeper": "Погрузитесь глубже",
"exploration_progress": "Прогресс исследования",
"explore_country": "Исследуйте страну",
"globe_spin_error_desc": "Ошибка извлечения данных спиновых глобусов",
"hide_globe_spin": "Скрыть глобус спин",
"in": "в",
"loading_globe_spin": "Загрузка глобуса спина",
"no_globe_spin_data": "Нет данных о вращении Globe",
"show_globe_spin": "Показать Globe Spin",
"spin_again": "Снова спите",
"spinning_globe": "Вращающийся глобус",
"try_again": "Попробуйте еще раз",
"your_random_adventure_awaits": "Ваше случайное приключение ждет!"
},
"auth": {
"username": "Имя пользователя",
@@ -874,7 +889,13 @@
"available": "Доступный",
"pending": "В ожидании",
"revoke_invite": "Отменить приглашение",
"send_invite": "Отправить приглашение"
"send_invite": "Отправить приглашение",
"available_users": "Доступные пользователи",
"invite_failed": "Приглашение не удалось",
"invite_revoked": "Пригласить отменен",
"invite_sent": "Пригласить отправлено",
"revoke_failed": "Отмена не удалась",
"unshare_failed": "UNSHARE не удалось"
},
"languages": {},
"profile": {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1018
frontend/src/locales/tr.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -561,7 +561,22 @@
"unvisited_first": "未访问优先",
"visited_first": "已访问优先",
"total_items": "总项目",
"getting_location_details": "获取地点详细信息"
"getting_location_details": "获取地点详细信息",
"cities_available": "可用的城市",
"destination_revealed": "目的地揭示了!",
"dive_deeper": "深入潜水",
"exploration_progress": "勘探进度",
"explore_country": "探索国家",
"globe_spin_error_desc": "错误获取地球旋转数据",
"hide_globe_spin": "隐藏环球旋转",
"in": "在",
"loading_globe_spin": "加载地球旋转",
"no_globe_spin_data": "没有地球旋转数据",
"show_globe_spin": "显示环球旋转",
"spin_again": "再次旋转",
"spinning_globe": "旋转地球",
"try_again": "再试一次",
"your_random_adventure_awaits": "您的随机冒险在等待!"
},
"users": {
"no_users_found": "未找到已公开的用户。"
@@ -854,8 +869,12 @@
"pending": "待办的",
"revoke_invite": "撤销邀请",
"send_invite": "发送邀请",
"available_users": "可邀请的用户",
"no_available_users": "没有可邀请的用户"
"available_users": "可用户",
"invite_failed": "邀请失败",
"invite_revoked": "邀请被撤销",
"invite_sent": "邀请发送",
"revoke_failed": "撤销失败",
"unshare_failed": "没有共享失败"
},
"profile": {
"member_since": "会员自",

View File

@@ -21,6 +21,7 @@
register('ar', () => import('../locales/ar.json'));
register('pt-br', () => import('../locales/pt-br.json'));
register('sk', () => import('../locales/sk.json'));
register('tr', () => import('../locales/tr.json'));
let locales = [
'en',
@@ -38,7 +39,8 @@
'ja',
'ar',
'pt-br',
'sk'
'sk',
'tr'
];
if (browser) {

View File

@@ -8,11 +8,14 @@ const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
let sessionId = event.cookies.get('sessionid');
let visitedFetch = await fetch(`${endpoint}/api/locations/all/?include_collections=true`, {
headers: {
Cookie: `sessionid=${sessionId}`
let visitedFetch = await fetch(
`${endpoint}/api/locations/all?include_collections=true&nested=true&allowed_nested_fields=visits`,
{
headers: {
Cookie: `sessionid=${sessionId}`
}
}
});
);
let adventures = (await visitedFetch.json()) as Location[];
// Get user's local timezone as fallback

View File

@@ -12,88 +12,66 @@ const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
if (!event.locals.user) {
return redirect(302, '/login');
} else {
let next = null;
let previous = null;
let count = 0;
let collections: Location[] = [];
let sessionId = event.cookies.get('sessionid');
}
// Get sorting parameters from URL
const order_by = event.url.searchParams.get('order_by') || 'updated_at';
const order_direction = event.url.searchParams.get('order_direction') || 'desc';
const page = event.url.searchParams.get('page') || '1';
const sessionId = event.cookies.get('sessionid');
if (!sessionId) {
return redirect(302, '/login');
}
// Build API URL with parameters
let apiUrl = `${serverEndpoint}/api/collections/?order_by=${order_by}&order_direction=${order_direction}&page=${page}`;
// Get sorting parameters from URL
const order_by = event.url.searchParams.get('order_by') || 'updated_at';
const order_direction = event.url.searchParams.get('order_direction') || 'desc';
const page = event.url.searchParams.get('page') || '1';
const currentPage = parseInt(page);
let initialFetch = await fetch(apiUrl, {
headers: {
Cookie: `sessionid=${sessionId}`
},
credentials: 'include'
});
if (!initialFetch.ok) {
console.error('Failed to fetch collections');
return redirect(302, '/login');
} else {
let res = await initialFetch.json();
let visited = res.results as Location[];
next = res.next;
previous = res.previous;
count = res.count;
collections = [...collections, ...visited];
}
// Common headers for all requests
const headers = {
Cookie: `sessionid=${sessionId}`
};
let sharedRes = await fetch(`${serverEndpoint}/api/collections/shared/`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
});
if (!sharedRes.ok) {
console.error('Failed to fetch shared collections');
// Build API URL with nested=true for lighter payload
const apiUrl = `${serverEndpoint}/api/collections/?order_by=${order_by}&order_direction=${order_direction}&page=${page}&nested=true`;
try {
// Execute all API calls in parallel
const [collectionsRes, sharedRes, archivedRes, invitesRes] = await Promise.all([
fetch(apiUrl, { headers, credentials: 'include' }),
fetch(`${serverEndpoint}/api/collections/shared/?nested=true`, { headers }),
fetch(`${serverEndpoint}/api/collections/archived/?nested=true`, { headers }),
fetch(`${serverEndpoint}/api/collections/invites/`, { headers })
]);
// Check if main collections request failed (most critical)
if (!collectionsRes.ok) {
console.error('Failed to fetch collections:', collectionsRes.status);
return redirect(302, '/login');
}
let sharedCollections = (await sharedRes.json()) as Collection[];
let archivedRes = await fetch(`${serverEndpoint}/api/collections/archived/`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
});
if (!archivedRes.ok) {
console.error('Failed to fetch archived collections');
return redirect(302, '/login');
}
let archivedCollections = (await archivedRes.json()) as Collection[];
let inviteRes = await fetch(`${serverEndpoint}/api/collections/invites/`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
});
if (!inviteRes.ok) {
console.error('Failed to fetch invites');
return redirect(302, '/login');
}
let invites = await inviteRes.json();
// Calculate current page from URL
const currentPage = parseInt(page);
// Parse responses in parallel
const [collectionsData, sharedData, archivedData, invitesData] = await Promise.all([
collectionsRes.json(),
sharedRes.ok ? sharedRes.json() : [],
archivedRes.ok ? archivedRes.json() : [],
invitesRes.ok ? invitesRes.json() : []
]);
return {
props: {
adventures: collections,
next,
previous,
count,
sharedCollections,
adventures: collectionsData.results as Location[],
next: collectionsData.next,
previous: collectionsData.previous,
count: collectionsData.count,
sharedCollections: sharedData as Collection[],
currentPage,
order_by,
order_direction,
archivedCollections,
invites
archivedCollections: archivedData as Collection[],
invites: invitesData
}
};
} catch (error) {
console.error('Error fetching data:', error);
return redirect(302, '/login');
}
}) satisfies PageServerLoad;

View File

@@ -33,7 +33,8 @@
groupLodgingByDate,
LODGING_TYPES_ICONS,
getBasemapUrl,
isAllDay
isAllDay,
getActivityColor
} from '$lib';
import { formatDateInTimezone, formatAllDayDate } from '$lib/dateUtils';
@@ -1230,7 +1231,7 @@
</div>
<div>
<!-- Duration -->
{Math.round(
{Math.floor(
(new Date(orderedItem.end).getTime() -
new Date(orderedItem.start).getTime()) /
1000 /
@@ -1375,6 +1376,39 @@
</Marker>
{/if}
{/each}
<!-- Shows activity GPX on the map -->
{#each adventures as adventure}
{#each adventure.visits as visit}
{#each visit.activities as activity}
{#if activity.geojson}
<GeoJSON data={activity.geojson}>
<LineLayer
paint={{
'line-color': getActivityColor(activity.sport_type),
'line-width': 3,
'line-opacity': 0.8
}}
/>
</GeoJSON>
{/if}
{/each}
{/each}
{#each adventure.attachments as attachment}
{#if attachment.geojson}
<GeoJSON data={attachment.geojson}>
<LineLayer
paint={{
'line-color': '#00FF00',
'line-width': 2,
'line-opacity': 0.6
}}
/>
</GeoJSON>
{/if}
{/each}
{/each}
{#if lineData && collection.start_date && collection.end_date}
<GeoJSON data={lineData}>
<LineLayer

View File

@@ -1,7 +1,7 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
import type { Location, VisitedRegion } from '$lib/types';
import type { Location, Pin, VisitedRegion } from '$lib/types';
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
@@ -9,7 +9,7 @@ export const load = (async (event) => {
return redirect(302, '/login');
} else {
let sessionId = event.cookies.get('sessionid');
let visitedFetch = await fetch(`${endpoint}/api/locations/all/?include_collections=true`, {
let pinFetch = await fetch(`${endpoint}/api/locations/pins/`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
@@ -22,19 +22,19 @@ export const load = (async (event) => {
});
let visitedRegions = (await visitedRegionsFetch.json()) as VisitedRegion[];
let adventures = (await visitedFetch.json()) as Location[];
let pins = (await pinFetch.json()) as Pin[];
if (!visitedRegionsFetch.ok) {
console.error('Failed to fetch visited regions');
return redirect(302, '/login');
} else if (!visitedFetch.ok) {
console.error('Failed to fetch visited adventures');
} else if (!pinFetch.ok) {
console.error('Failed to fetch location pins');
return redirect(302, '/login');
} else {
return {
props: {
visitedRegions,
adventures
pins
}
};
}

View File

@@ -9,7 +9,7 @@
LineLayer
} from 'svelte-maplibre';
import { t } from 'svelte-i18n';
import type { Activity, Location, VisitedCity, VisitedRegion } from '$lib/types.js';
import type { Activity, Location, VisitedCity, VisitedRegion, Pin } from '$lib/types.js';
import CardCarousel from '$lib/components/CardCarousel.svelte';
import { goto } from '$app/navigation';
import { basemapOptions, getActivityColor, getBasemapLabel, getBasemapUrl } from '$lib';
@@ -20,7 +20,7 @@
import Plus from '~icons/mdi/plus';
import Clear from '~icons/mdi/close';
import Eye from '~icons/mdi/eye';
import Pin from '~icons/mdi/map-marker';
import PinIcon from '~icons/mdi/map-marker';
import Calendar from '~icons/mdi/calendar';
import LocationIcon from '~icons/mdi/crosshairs-gps';
import NewLocationModal from '$lib/components/NewLocationModal.svelte';
@@ -35,17 +35,16 @@
let showCities: boolean = false;
let sidebarOpen: boolean = false;
let basemapType: string = 'default'; // default
let basemapType: string = 'default';
export let initialLatLng: { lat: number; lng: number } | null = null;
let visitedRegions: VisitedRegion[] = data.props.visitedRegions;
let visitedCities: VisitedCity[] = [];
let adventures: Location[] = data.props.adventures;
let pins: Pin[] = data.props.pins; // Lightweight pin objects
let activities: Activity[] = [];
let filteredAdventures = adventures;
let filteredPins = pins;
let showVisited: boolean = true;
let showPlanned: boolean = true;
@@ -54,23 +53,25 @@
let newLongitude: number | null = null;
let newLatitude: number | null = null;
let isPopupOpen = false;
// Cache for full location data
let locationCache: Map<string, Location> = new Map();
let loadingLocations: Set<string> = new Set();
let locationBeingUpdated: Location | undefined = undefined;
// Statistics
$: totalAdventures = adventures.length;
$: visitedAdventures = adventures.filter((adventure) => adventure.is_visited).length;
$: plannedAdventures = adventures.filter((adventure) => !adventure.is_visited).length;
$: totalAdventures = pins.length;
$: visitedAdventures = pins.filter((pin) => pin.is_visited).length;
$: plannedAdventures = pins.filter((pin) => !pin.is_visited).length;
$: totalRegions = visitedRegions.length;
// Get unique categories for filtering
$: categories = [
...new Set(adventures.map((adventure) => adventure.category?.display_name).filter(Boolean))
];
$: categories = [...new Set(pins.map((pin) => pin.category?.display_name).filter(Boolean))];
// Updates the filtered adventures based on the checkboxes
// Updates the filtered pins based on the checkboxes
$: {
filteredAdventures = adventures.filter(
(adventure) => (showVisited && adventure.is_visited) || (showPlanned && !adventure.is_visited)
filteredPins = pins.filter(
(pin) => (showVisited && pin.is_visited === true) || (showPlanned && pin.is_visited !== true)
);
}
@@ -82,22 +83,37 @@
}
}
let locationBeingUpdated: Location | undefined = undefined;
// Sync the locationBeingUpdated with the adventures array
// Sync the locationBeingUpdated with the pins array
$: {
if (locationBeingUpdated && locationBeingUpdated.id) {
const index = adventures.findIndex((adventure) => adventure.id === locationBeingUpdated?.id);
const index = pins.findIndex((pin) => pin.id === locationBeingUpdated?.id);
if (index !== -1) {
adventures[index] = { ...locationBeingUpdated };
adventures = adventures; // Trigger reactivity
// Update existing pin with new data
pins[index] = {
id: locationBeingUpdated.id,
name: locationBeingUpdated.name,
latitude: locationBeingUpdated.latitude?.toString() || '',
longitude: locationBeingUpdated.longitude?.toString() || '',
is_visited: locationBeingUpdated.is_visited,
category: locationBeingUpdated.category
};
pins = pins; // Trigger reactivity
} else {
adventures = [{ ...locationBeingUpdated }, ...adventures];
if (data.props.adventures) {
data.props.adventures = adventures; // Update data.props.adventure.locations as well
}
// Add new pin
const newPin: Pin = {
id: locationBeingUpdated.id,
name: locationBeingUpdated.name,
latitude: locationBeingUpdated.latitude?.toString() || '',
longitude: locationBeingUpdated.longitude?.toString() || '',
is_visited: locationBeingUpdated.is_visited,
category: locationBeingUpdated.category
};
pins = [newPin, ...pins];
}
// Also update the cache
locationCache.set(locationBeingUpdated.id, locationBeingUpdated);
}
}
@@ -124,6 +140,36 @@
visitedCities = await response.json();
}
async function fetchLocationDetails(locationId: string): Promise<Location | null> {
// Check cache first
if (locationCache.has(locationId)) {
return locationCache.get(locationId)!;
}
// Prevent duplicate requests
if (loadingLocations.has(locationId)) {
return null;
}
try {
loadingLocations.add(locationId);
const response = await fetch(`/api/locations/${locationId}`);
if (!response.ok) {
throw new Error(`Failed to fetch location: ${response.statusText}`);
}
const location: Location = await response.json();
locationCache.set(locationId, location);
return location;
} catch (error) {
console.error('Error fetching location details:', error);
return null;
} finally {
loadingLocations.delete(locationId);
}
}
function addMarker(e: { detail: { lngLat: { lng: any; lat: any } } }) {
newMarker = null;
newMarker = { lngLat: e.detail.lngLat };
@@ -137,22 +183,43 @@
}
function createNewAdventure(event: CustomEvent) {
adventures = [...adventures, event.detail];
const location: Location = event.detail;
// Add to pins array
const newPin: Pin = {
id: location.id,
name: location.name,
latitude: location.latitude?.toString() || '',
longitude: location.longitude?.toString() || '',
is_visited: location.is_visited,
category: location.category
};
pins = [...pins, newPin];
// Add to cache
locationCache.set(location.id, location);
newMarker = null;
createModalOpen = false;
}
function togglePopup() {
isPopupOpen = !isPopupOpen;
}
function toggleSidebar() {
sidebarOpen = !sidebarOpen;
}
function clearMarker() {
newMarker = null;
}
// Function to handle popup opening - only fetch when actually needed
let openPopups = new Set<string>();
function handlePopupOpen(pinId: string) {
openPopups.add(pinId);
openPopups = openPopups; // Trigger reactivity
}
function handlePopupClose(pinId: string) {
openPopups.delete(pinId);
openPopups = openPopups; // Trigger reactivity
}
</script>
<svelte:head>
@@ -170,7 +237,10 @@
<div class="container mx-auto px-6 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<button class="btn btn-ghost btn-square lg:hidden" on:click={toggleSidebar}>
<button
class="btn btn-ghost btn-square lg:hidden"
on:click={() => (sidebarOpen = !sidebarOpen)}
>
<Filter class="w-5 h-5" />
</button>
<div class="flex items-center gap-3">
@@ -182,7 +252,7 @@
{$t('map.location_map')}
</h1>
<p class="text-sm text-base-content/60">
{filteredAdventures.length}
{filteredPins.length}
{$t('worldtravel.of')}
{totalAdventures}
{$t('map.locations_shown')}
@@ -252,101 +322,146 @@
class="w-full h-full min-h-[70vh] rounded-lg"
standardControls
>
{#each filteredAdventures as adventure}
{#if adventure.latitude && adventure.longitude}
{#each filteredPins as pin}
{#if pin.latitude && pin.longitude}
<Marker
lngLat={[adventure.longitude, adventure.latitude]}
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 shadow-lg cursor-pointer hover:scale-110 transition-transform {adventure.is_visited
? 'bg-red-300 hover:bg-red-400'
: 'bg-blue-300 hover:bg-blue-400'} text-black focus:outline-6 focus:outline-black"
on:click={togglePopup}
lngLat={[parseFloat(pin.longitude), parseFloat(pin.latitude)]}
class="grid h-8 w-8 place-items-center rounded-full border-2 border-white shadow-lg cursor-pointer hover:scale-110 transition-all duration-200 {pin.is_visited
? 'bg-gradient-to-br from-emerald-400 to-emerald-600 hover:from-emerald-500 hover:to-emerald-700'
: 'bg-gradient-to-br from-blue-400 to-blue-600 hover:from-blue-500 hover:to-blue-700'} text-white focus:outline-4 focus:outline-primary/50"
>
<span class="text-xl">
{adventure.category?.icon || '📍'}
{pin.category?.icon || '📍'}
</span>
{#if isPopupOpen}
<Popup
openOn="click"
offset={[0, -10]}
on:close={() => (isPopupOpen = false)}
>
<div class="min-w-64 max-w-sm">
{#if adventure.images && adventure.images.length > 0}
<div class="mb-3">
<CardCarousel
images={adventure.images}
name={adventure.name}
icon={adventure?.category?.icon}
/>
<Popup
openOn="click"
offset={[0, -10]}
on:open={() => handlePopupOpen(pin.id)}
on:close={() => handlePopupClose(pin.id)}
>
<div class="min-w-64 max-w-sm">
{#if openPopups.has(pin.id)}
{#await fetchLocationDetails(pin.id)}
<div class="flex items-center justify-center p-4">
<span class="loading loading-spinner loading-sm"></span>
<span class="ml-2 text-sm">Loading details...</span>
</div>
{/if}
<div class="space-y-2">
<div class="text-lg text-black font-bold">{adventure.name}</div>
<div class="flex items-center gap-2">
<span
class="badge {adventure.is_visited
? 'badge-success'
: 'badge-info'} badge-sm"
>
{adventure.is_visited
? $t('adventures.visited')
: $t('adventures.planned')}
</span>
{#if adventure.category}
<span class="badge badge-outline badge-sm">
{adventure.category.display_name}
{adventure.category.icon}
</span>
{:then location}
{#if location}
{#if location.images && location.images.length > 0}
<div class="mb-3">
<CardCarousel
images={location.images}
name={location.name}
icon={location?.category?.icon}
/>
</div>
{/if}
</div>
{#if adventure.visits && adventure.visits.length > 0}
<div class="text-black text-sm space-y-1">
{#each adventure.visits as visit}
<div class="flex items-center gap-1">
<Calendar class="w-3 h-3" />
<span>
{visit.start_date
? new Date(visit.start_date).toLocaleDateString(undefined, {
timeZone: 'UTC'
})
: ''}
{visit.end_date &&
visit.end_date !== '' &&
visit.end_date !== visit.start_date
? ' - ' +
new Date(visit.end_date).toLocaleDateString(undefined, {
timeZone: 'UTC'
})
: ''}
<div class="space-y-2">
<div class="text-lg text-black font-bold">{location.name}</div>
<div class="flex items-center gap-2">
<span
class="badge {location.is_visited
? 'badge-success'
: 'badge-info'} badge-sm"
>
{location.is_visited
? $t('adventures.visited')
: $t('adventures.planned')}
</span>
{#if location.category}
<span class="badge badge-outline badge-sm">
{location.category.display_name}
{location.category.icon}
</span>
{/if}
</div>
{#if location.visits && location.visits.length > 0}
<div class="text-black text-sm space-y-1">
{#each location.visits as visit}
<div class="flex items-center gap-1">
<Calendar class="w-3 h-3" />
<span>
{visit.start_date
? new Date(visit.start_date).toLocaleDateString(
undefined,
{
timeZone: 'UTC'
}
)
: ''}
{visit.end_date &&
visit.end_date !== '' &&
visit.end_date !== visit.start_date
? ' - ' +
new Date(visit.end_date).toLocaleDateString(
undefined,
{
timeZone: 'UTC'
}
)
: ''}
</span>
</div>
{/each}
</div>
{/each}
{/if}
<div class="flex flex-col gap-2 pt-2">
{#if location.longitude && location.latitude}
<a
class="btn btn-outline btn-sm gap-2"
href={`https://maps.apple.com/?q=${location.latitude},${location.longitude}`}
target="_blank"
rel="noopener noreferrer"
>
<LocationIcon class="w-4 h-4" />
{$t('adventures.open_in_maps')}
</a>
{/if}
<button
class="btn btn-primary btn-sm gap-2"
on:click={() => goto(`/locations/${location.id}`)}
>
<Eye class="w-4 h-4" />
{$t('map.view_details')}
</button>
</div>
</div>
{:else}
<div class="p-4 text-center">
<div class="text-lg text-black font-bold">{pin.name}</div>
<div class="text-sm text-gray-600">Failed to load details</div>
<button
class="btn btn-primary btn-sm gap-2 mt-2"
on:click={() => goto(`/locations/${pin.id}`)}
>
<Eye class="w-4 h-4" />
{$t('map.view_details')}
</button>
</div>
{/if}
<div class="flex flex-col gap-2 pt-2">
{#if adventure.longitude && adventure.latitude}
<a
class="btn btn-outline btn-sm gap-2"
href={`https://maps.apple.com/?q=${adventure.latitude},${adventure.longitude}`}
target="_blank"
rel="noopener noreferrer"
>
<LocationIcon class="w-4 h-4" />
{$t('adventures.open_in_maps')}
</a>
{/if}
{:catch error}
<div class="p-4 text-center">
<div class="text-lg text-black font-bold">{pin.name}</div>
<div class="text-sm text-red-600">Error loading details</div>
<button
class="btn btn-primary btn-sm gap-2"
on:click={() => goto(`/locations/${adventure.id}`)}
class="btn btn-primary btn-sm gap-2 mt-2"
on:click={() => goto(`/locations/${pin.id}`)}
>
<Eye class="w-4 h-4" />
{$t('map.view_details')}
</button>
</div>
{/await}
{:else}
<div class="p-4 text-center">
<div class="text-lg text-black font-bold">{pin.name}</div>
<div class="text-sm text-gray-600">Click to load details...</div>
</div>
</div>
</Popup>
{/if}
{/if}
</div>
</Popup>
</Marker>
{/if}
{/each}
@@ -551,7 +666,7 @@
{#if newMarker}
<div class="space-y-3">
<div class="alert alert-info">
<Pin class="w-4 h-4" />
<PinIcon class="w-4 h-4" />
<span class="text-sm">{$t('map.marker_placed_on_map')}</span>
</div>
<button type="button" class="btn btn-primary w-full gap-2" on:click={newAdventure}>

View File

@@ -26,6 +26,7 @@
const allCountries: Country[] = data.props?.countries || [];
let worldSubregions: string[] = [];
let showMap: boolean = false;
let showGlobeSpin: boolean = false;
let sidebarOpen = false;
worldSubregions = [...new Set(allCountries.map((country) => country.subregion))];
@@ -74,6 +75,39 @@
}
}
// when isGlobeSpin is enabled, fetch /api/globespin/
type GlobeSpinData = {
country: {
flag_url: string;
name: string;
country_code: string;
num_visits: number;
subregion: string;
capital: string;
num_regions: number;
};
region: { name: string; num_cities: number };
city: { name: string; region_name: string };
};
let globeSpinData: GlobeSpinData | null = null;
let isLoadingGlobeSpin = false;
async function fetchGlobeSpin() {
isLoadingGlobeSpin = true;
try {
const response = await fetch('/api/globespin/');
if (response.ok) {
globeSpinData = await response.json();
} else {
console.error('Failed to fetch globe spin data');
}
} catch (error) {
console.error('Error fetching globe spin data:', error);
} finally {
isLoadingGlobeSpin = false;
}
}
function toggleSidebar() {
sidebarOpen = !sidebarOpen;
}
@@ -171,6 +205,24 @@
<span class="hidden sm:inline">{$t('worldtravel.show_map')}</span>
{/if}
</button>
<!-- Globe Spin Toggle -->
<button
class="btn btn-outline gap-2 {showGlobeSpin ? 'btn-active' : ''}"
on:click={() => {
showGlobeSpin = !showGlobeSpin;
if (showGlobeSpin) {
fetchGlobeSpin();
}
}}
>
{#if showGlobeSpin}
<Globe class="w-4 h-4" />
<span class="hidden sm:inline">{$t('worldtravel.hide_globe_spin')}</span>
{:else}
<Globe class="w-4 h-4" />
<span class="hidden sm:inline">{$t('worldtravel.show_globe_spin')}</span>
{/if}
</button>
</div>
<!-- Filter Chips -->
@@ -264,6 +316,257 @@
</div>
{/if}
<!-- Globe Spin Section -->
{#if showGlobeSpin}
<div class="container mx-auto px-6 py-4">
<div class="card bg-base-100 shadow-xl overflow-hidden">
<div class="card-body p-6">
{#if isLoadingGlobeSpin}
<!-- Loading State with Spinning Globe -->
<div class="flex flex-col items-center py-12">
<div class="relative">
<!-- Spinning globe with pulse effect -->
<div class="relative animate-spin" style="animation-duration: 3s;">
<div
class="w-24 h-24 rounded-full bg-gradient-to-br from-primary/20 to-accent/30 flex items-center justify-center border-4 border-primary/30"
>
<Globe class="w-12 h-12 text-primary" />
</div>
<!-- Orbit rings -->
<div
class="absolute inset-0 rounded-full border-2 border-dashed border-primary/20 animate-pulse"
></div>
<div
class="absolute -inset-2 rounded-full border border-dashed border-accent/20 animate-pulse"
style="animation-delay: 0.5s;"
></div>
</div>
<!-- Sparkle effects -->
<div
class="absolute -top-2 -right-2 w-3 h-3 bg-yellow-400 rounded-full animate-ping"
></div>
<div
class="absolute -bottom-3 -left-3 w-2 h-2 bg-blue-400 rounded-full animate-ping"
style="animation-delay: 1s;"
></div>
<div
class="absolute top-1/2 -right-4 w-1.5 h-1.5 bg-green-400 rounded-full animate-ping"
style="animation-delay: 2s;"
></div>
</div>
<div class="mt-6 text-center">
<h3 class="text-xl font-bold text-primary mb-2">
{$t('worldtravel.spinning_globe') + '...'}
</h3>
<p class="text-base-content/70 animate-pulse">
{$t('worldtravel.loading_globe_spin')}
</p>
<div class="flex items-center justify-center gap-1 mt-3">
<div class="w-2 h-2 bg-primary rounded-full animate-bounce"></div>
<div
class="w-2 h-2 bg-primary rounded-full animate-bounce"
style="animation-delay: 0.2s;"
></div>
<div
class="w-2 h-2 bg-primary rounded-full animate-bounce"
style="animation-delay: 0.4s;"
></div>
</div>
</div>
</div>
{:else if globeSpinData}
<!-- Result Display with Amazing Animations -->
<div class="text-center">
<div class="mb-6">
<h3
class="text-2xl font-bold text-primary mb-2 flex items-center justify-center gap-3"
>
<Globe class="w-8 h-8 animate-spin" style="animation-duration: 4s;" />
{$t('worldtravel.destination_revealed')}
<Globe
class="w-8 h-8 animate-spin"
style="animation-duration: 4s; animation-direction: reverse;"
/>
</h3>
<p class="text-base-content/60">
{$t('worldtravel.your_random_adventure_awaits')}
</p>
</div>
<!-- Country Card with Entrance Animation -->
<div class="animate-slideInUp" style="animation-duration: 0.8s;">
<!-- Flag with Reveal Effect -->
<div class="relative mb-6 mx-auto w-fit">
<div
class="relative overflow-hidden rounded-2xl shadow-2xl border-4 border-primary/20 hover:border-primary/40 transition-colors duration-300"
>
<img
src={globeSpinData.country.flag_url}
alt="{globeSpinData.country.name} flag"
class="w-64 h-40 object-cover hover:scale-105 transition-transform duration-500"
/>
<!-- Shimmer overlay -->
<div
class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full animate-shimmer"
></div>
</div>
<!-- Floating badges -->
<div
class="absolute -top-3 -right-3 badge badge-primary badge-lg animate-bounce shadow-lg"
>
{globeSpinData.country.country_code}
</div>
{#if globeSpinData.country.num_visits > 0}
<div
class="absolute -top-3 -left-3 badge badge-success badge-lg animate-pulse shadow-lg"
>
<Check class="w-4 h-4 mr-1" />
{$t('adventures.visited')}
</div>
{/if}
</div>
<!-- Country Info -->
<div class="space-y-4 animate-fadeInUp" style="animation-delay: 0.2s;">
<h2
class="text-4xl font-bold text-primary bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent pb-2"
>
{globeSpinData.country.name}
</h2>
<div class="flex flex-wrap justify-center gap-4">
<div class="badge badge-lg badge-outline gap-2">
<Pin class="w-4 h-4" />
{globeSpinData.country.subregion}
</div>
{#if globeSpinData.country.capital}
<div class="badge badge-lg badge-outline gap-2">
<Globe class="w-4 h-4" />
{globeSpinData.country.capital}
</div>
{/if}
</div>
<!-- Progress Info -->
<div
class="card bg-gradient-to-br from-base-200/50 to-base-300/30 p-4 max-w-md mx-auto"
>
<div class="flex justify-between items-center mb-2">
<span class="text-sm font-medium"
>{$t('worldtravel.exploration_progress')}</span
>
<span class="text-lg font-bold text-primary">
{globeSpinData.country.num_visits}/{globeSpinData.country.num_regions}
</span>
</div>
<progress
class="progress progress-primary w-full"
value={globeSpinData.country.num_visits}
max={globeSpinData.country.num_regions}
></progress>
<div class="text-xs text-base-content/60 mt-1">
{Math.round(
(globeSpinData.country.num_visits / globeSpinData.country.num_regions) *
100
)}% explored
</div>
</div>
</div>
</div>
<!-- Region & City Info (if available) -->
{#if globeSpinData.region || globeSpinData.city}
<div class="mt-8 space-y-4 animate-fadeInUp" style="animation-delay: 0.4s;">
<div class="divider">
<span class="text-primary font-semibold"
>{$t('worldtravel.dive_deeper')}</span
>
</div>
<div class="grid md:grid-cols-2 gap-4 max-w-2xl mx-auto">
{#if globeSpinData.region}
<div
class="card bg-gradient-to-br from-accent/10 to-secondary/10 border border-accent/20"
>
<div class="card-body p-4">
<h4 class="font-bold text-accent flex items-center gap-2">
<Pin class="w-5 h-5" />
{$t('adventures.region')}
</h4>
<p class="text-lg font-semibold">{globeSpinData.region.name}</p>
<p class="text-sm text-base-content/60">
{globeSpinData.region.num_cities}
{$t('worldtravel.cities_available')}
</p>
</div>
</div>
{/if}
{#if globeSpinData.city}
<div
class="card bg-gradient-to-br from-success/10 to-info/10 border border-success/20"
>
<div class="card-body p-4">
<h4 class="font-bold text-success flex items-center gap-2">
<Map class="w-5 h-5" />
{$t('adventures.city')}
</h4>
<p class="text-lg font-semibold">{globeSpinData.city.name}</p>
<p class="text-sm text-base-content/60">
{$t('worldtravel.in')}
{globeSpinData.city.region_name}
</p>
</div>
</div>
{/if}
</div>
</div>
{/if}
<!-- Action Buttons -->
<div
class="mt-8 flex flex-wrap justify-center gap-4 animate-fadeInUp"
style="animation-delay: 0.6s;"
>
<a
href="/worldtravel/{globeSpinData.country.country_code}"
class="btn btn-primary btn-lg gap-2 shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-105"
>
<Globe class="w-5 h-5" />
{$t('worldtravel.explore_country')}
</a>
<button
class="btn btn-outline btn-lg gap-2 hover:scale-105 transition-all duration-300"
on:click={fetchGlobeSpin}
>
<Globe class="w-5 h-5 animate-spin" style="animation-duration: 2s;" />
{$t('worldtravel.spin_again')}
</button>
</div>
</div>
{:else}
<!-- No Data State -->
<div class="flex flex-col items-center py-12">
<div class="p-6 bg-error/10 rounded-2xl mb-6">
<Cancel class="w-16 h-16 text-error/50" />
</div>
<h3 class="text-xl font-semibold text-base-content/70 mb-2">
{$t('worldtravel.no_globe_spin_data')}
</h3>
<p class="text-base-content/50 text-center max-w-md mb-6">
{$t('worldtravel.globe_spin_error_desc')}
</p>
<button class="btn btn-primary gap-2" on:click={fetchGlobeSpin}>
<Globe class="w-4 h-4" />
{$t('worldtravel.try_again')}
</button>
</div>
{/if}
</div>
</div>
</div>
{/if}
<!-- Main Content -->
<div class="container mx-auto px-6 py-8">
{#if filteredCountries.length === 0}
@@ -433,3 +736,48 @@
</div>
</div>
</div>
<style>
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.animate-slideInUp {
animation: slideInUp ease-out forwards;
}
.animate-fadeInUp {
animation: fadeInUp ease-out forwards;
}
.animate-shimmer {
animation: shimmer 2s infinite;
}
</style>

View File

@@ -5,56 +5,56 @@ import { build, files, version } from '$service-worker';
const CACHE = `cache-${version}`;
const ASSETS = [
...build, // the app itself
...files // everything in `static`
...build, // the app itself
...files // everything in `static`
];
self.addEventListener('install', (event) => {
// Create a new cache and add all files to it
async function addFilesToCache() {
const cache = await caches.open(CACHE);
await cache.addAll(ASSETS);
}
event.waitUntil(addFilesToCache());
// Create a new cache and add all files to it
async function addFilesToCache() {
const cache = await caches.open(CACHE);
await cache.addAll(ASSETS);
}
event.waitUntil(addFilesToCache());
});
self.addEventListener('activate', (event) => {
// Remove previous cached data from disk
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) await caches.delete(key);
}
}
event.waitUntil(deleteOldCaches());
// Remove previous cached data from disk
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) await caches.delete(key);
}
}
event.waitUntil(deleteOldCaches());
});
self.addEventListener('fetch', (event) => {
// ignore POST requests, etc
if (event.request.method !== 'GET') return;
// ignore POST requests, etc
if (event.request.method !== 'GET') return;
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
return cache.match(url.pathname);
}
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
return cache.match(url.pathname);
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.request);
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.request);
if (response.status === 200) {
cache.put(event.request, response.clone());
}
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
} catch {
return cache.match(event.request);
}
}
return response;
} catch {
return cache.match(event.request);
}
}
event.respondWith(respond());
event.respondWith(respond());
});

View File

@@ -1,16 +1,16 @@
{
"short_name": "AdventureLog",
"name": "AdventureLog",
"start_url": "/dashboard",
"icons": [
{
"src": "adventurelog.svg",
"type": "image/svg+xml",
"sizes": "any"
}
],
"background_color": "#2a323c",
"display": "standalone",
"scope": "/",
"description": "Self-hostable travel tracker and trip planner."
}
"short_name": "AdventureLog",
"name": "AdventureLog",
"start_url": "/dashboard",
"icons": [
{
"src": "adventurelog.svg",
"type": "image/svg+xml",
"sizes": "any"
}
],
"background_color": "#2a323c",
"display": "standalone",
"scope": "/",
"description": "Self-hostable travel tracker and trip planner."
}