mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-23 14:59:36 +02:00
Fix adventure grouping in collections
This commit is contained in:
parent
1eadac90f1
commit
b9cae8a687
4 changed files with 176 additions and 157 deletions
|
@ -1,10 +1,10 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %} {% block content %}
|
||||||
|
<!-- Main jumbotron for a primary marketing message or call to action -->
|
||||||
{% block content %}
|
<div class="jumbotron">
|
||||||
<!-- Main jumbotron for a primary marketing message or call to action -->
|
<h1>AdventureLog API Server</h1>
|
||||||
<div class="jumbotron">
|
<p>
|
||||||
<h1>AdventureLog API Server</h1>
|
<a class="btn btn-primary btn-lg" href="/admin" role="button">Admin Site</a>
|
||||||
<p>Welcome to the server side of AdventureLog!</p>
|
<a class="btn btn-secondary btn-lg" href="/docs" role="button">API Docs</a>
|
||||||
<p>This site is only ment for administrative users</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import type { Adventure, OpenStreetMapPlace, Point } from '$lib/types';
|
import type { Adventure, Collection, OpenStreetMapPlace, Point } from '$lib/types';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { addToast } from '$lib/toasts';
|
import { addToast } from '$lib/toasts';
|
||||||
|
@ -8,9 +8,7 @@
|
||||||
|
|
||||||
export let longitude: number | null = null;
|
export let longitude: number | null = null;
|
||||||
export let latitude: number | null = null;
|
export let latitude: number | null = null;
|
||||||
export let collection_id: string | null = null;
|
export let collection: Collection | null = null;
|
||||||
|
|
||||||
export let is_collection: boolean = false;
|
|
||||||
|
|
||||||
import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre';
|
import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre';
|
||||||
|
|
||||||
|
@ -19,21 +17,14 @@
|
||||||
let images: { id: string; image: string }[] = [];
|
let images: { id: string; image: string }[] = [];
|
||||||
let warningMessage: string = '';
|
let warningMessage: string = '';
|
||||||
|
|
||||||
import Earth from '~icons/mdi/earth';
|
|
||||||
import ActivityComplete from './ActivityComplete.svelte';
|
import ActivityComplete from './ActivityComplete.svelte';
|
||||||
import { appVersion } from '$lib/config';
|
import { appVersion } from '$lib/config';
|
||||||
import { ADVENTURE_TYPES } from '$lib';
|
import { ADVENTURE_TYPES } from '$lib';
|
||||||
|
|
||||||
export let startDate: string | null = null;
|
|
||||||
export let endDate: string | null = null;
|
|
||||||
|
|
||||||
let wikiError: string = '';
|
let wikiError: string = '';
|
||||||
|
|
||||||
let noPlaces: boolean = false;
|
let noPlaces: boolean = false;
|
||||||
|
|
||||||
let region_name: string | null = null;
|
|
||||||
let region_id: string | null = null;
|
|
||||||
|
|
||||||
let adventure: Adventure = {
|
let adventure: Adventure = {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -49,7 +40,7 @@
|
||||||
location: null,
|
location: null,
|
||||||
images: [],
|
images: [],
|
||||||
user_id: null,
|
user_id: null,
|
||||||
collection: collection_id || null
|
collection: collection?.id || null
|
||||||
};
|
};
|
||||||
|
|
||||||
export let adventureToEdit: Adventure | null = null;
|
export let adventureToEdit: Adventure | null = null;
|
||||||
|
@ -68,9 +59,8 @@
|
||||||
location: adventureToEdit?.location || null,
|
location: adventureToEdit?.location || null,
|
||||||
images: adventureToEdit?.images || [],
|
images: adventureToEdit?.images || [],
|
||||||
user_id: adventureToEdit?.user_id || null,
|
user_id: adventureToEdit?.user_id || null,
|
||||||
collection: adventureToEdit?.collection || collection_id || null,
|
collection: adventureToEdit?.collection || collection?.id || null,
|
||||||
visits: adventureToEdit?.visits || []
|
visits: adventureToEdit?.visits || []
|
||||||
//visits: []
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let markers: Point[] = [];
|
let markers: Point[] = [];
|
||||||
|
@ -91,7 +81,6 @@
|
||||||
activity_type: ''
|
activity_type: ''
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
checkPointInRegion();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (longitude && latitude) {
|
if (longitude && latitude) {
|
||||||
|
@ -109,8 +98,6 @@
|
||||||
function clearMap() {
|
function clearMap() {
|
||||||
console.log('CLEAR');
|
console.log('CLEAR');
|
||||||
markers = [];
|
markers = [];
|
||||||
region_id = null;
|
|
||||||
region_name = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageSearch: string = adventure.name || '';
|
let imageSearch: string = adventure.name || '';
|
||||||
|
@ -147,13 +134,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
|
||||||
if (adventure.type != 'visited') {
|
|
||||||
region_id = null;
|
|
||||||
region_name = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchImage() {
|
async function fetchImage() {
|
||||||
let res = await fetch(url);
|
let res = await fetch(url);
|
||||||
let data = await res.blob();
|
let data = await res.blob();
|
||||||
|
@ -236,6 +216,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let new_start_date: string = '';
|
||||||
|
let new_end_date: string = '';
|
||||||
|
let new_notes: string = '';
|
||||||
|
function addNewVisit() {
|
||||||
|
// check if start date is before end date
|
||||||
|
if (new_start_date > new_end_date) {
|
||||||
|
addToast('error', 'Start date must be before end date');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (new_start_date === '' || new_end_date === '') {
|
||||||
|
addToast('error', 'Please enter a start and end date');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (new_end_date && !new_start_date) {
|
||||||
|
addToast('error', 'Please enter a start date');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
adventure.visits = [
|
||||||
|
...adventure.visits,
|
||||||
|
{
|
||||||
|
start_date: new_start_date,
|
||||||
|
end_date: new_end_date,
|
||||||
|
notes: new_notes,
|
||||||
|
id: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
new_start_date = '';
|
||||||
|
new_end_date = '';
|
||||||
|
new_notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
async function reverseGeocode() {
|
async function reverseGeocode() {
|
||||||
let res = await fetch(
|
let res = await fetch(
|
||||||
`https://nominatim.openstreetmap.org/search?q=${adventure.latitude},${adventure.longitude}&format=jsonv2`,
|
`https://nominatim.openstreetmap.org/search?q=${adventure.latitude},${adventure.longitude}&format=jsonv2`,
|
||||||
|
@ -259,7 +270,6 @@
|
||||||
activity_type: data[0]?.type || ''
|
activity_type: data[0]?.type || ''
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
checkPointInRegion();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
@ -297,29 +307,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkPointInRegion() {
|
|
||||||
if (adventure.type == 'visited') {
|
|
||||||
let lat = markers[0].lngLat.lat;
|
|
||||||
let lon = markers[0].lngLat.lng;
|
|
||||||
let res = await fetch(`/api/countries/check_point_in_region/?lat=${lat}&lon=${lon}`);
|
|
||||||
let data = await res.json();
|
|
||||||
if (data.error) {
|
|
||||||
addToast('error', data.error);
|
|
||||||
} else {
|
|
||||||
if (data.in_region) {
|
|
||||||
region_name = data.region_name;
|
|
||||||
region_id = data.region_id;
|
|
||||||
} else {
|
|
||||||
region_id = null;
|
|
||||||
region_name = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
region_id = null;
|
|
||||||
region_name = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addMarker(e: CustomEvent<any>) {
|
async function addMarker(e: CustomEvent<any>) {
|
||||||
markers = [];
|
markers = [];
|
||||||
markers = [
|
markers = [
|
||||||
|
@ -331,8 +318,6 @@
|
||||||
activity_type: ''
|
activity_type: ''
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
checkPointInRegion();
|
|
||||||
|
|
||||||
console.log(markers);
|
console.log(markers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,20 +340,6 @@
|
||||||
|
|
||||||
async function handleSubmit(event: Event) {
|
async function handleSubmit(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (region_id && region_name) {
|
|
||||||
let res = await fetch(`/api/visitedregion/`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ region: region_id })
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
|
||||||
addToast('success', `Region ${region_name} marked as visited`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(adventure);
|
console.log(adventure);
|
||||||
if (adventure.id === '') {
|
if (adventure.id === '') {
|
||||||
let res = await fetch('/api/adventures', {
|
let res = await fetch('/api/adventures', {
|
||||||
|
@ -544,7 +515,7 @@
|
||||||
<p class="text-red-500">{wikiError}</p>
|
<p class="text-red-500">{wikiError}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if !collection_id}
|
{#if !collection?.id}
|
||||||
<div>
|
<div>
|
||||||
<div class="form-control flex items-start mt-1">
|
<div class="form-control flex items-start mt-1">
|
||||||
<label class="label cursor-pointer flex items-start space-x-2">
|
<label class="label cursor-pointer flex items-start space-x-2">
|
||||||
|
@ -563,34 +534,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="collapse collapse-plus bg-base-200 mb-4 overflow-visible">
|
|
||||||
<input type="checkbox" />
|
|
||||||
<div class="collapse-title text-xl font-medium">
|
|
||||||
Activity Types ({adventure.activity_types?.length || 0})
|
|
||||||
</div>
|
|
||||||
<div class="collapse-content">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="activity_types"
|
|
||||||
name="activity_types"
|
|
||||||
hidden
|
|
||||||
bind:value={adventure.activity_types}
|
|
||||||
class="input input-bordered w-full"
|
|
||||||
/>
|
|
||||||
<ActivityComplete bind:activities={adventure.activity_types} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="collapse collapse-plus bg-base-200 mb-4">
|
|
||||||
<input type="checkbox" />
|
|
||||||
<div class="collapse-title text-xl font-medium">
|
|
||||||
Visits ({adventure.visits.length})
|
|
||||||
</div>
|
|
||||||
<div class="collapse-content">
|
|
||||||
<p>Coming soon!</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="collapse collapse-plus bg-base-200 mb-4">
|
<div class="collapse collapse-plus bg-base-200 mb-4">
|
||||||
<input type="checkbox" />
|
<input type="checkbox" />
|
||||||
<div class="collapse-title text-xl font-medium">Location Information</div>
|
<div class="collapse-title text-xl font-medium">Location Information</div>
|
||||||
|
@ -640,7 +583,6 @@
|
||||||
activity_type: place.type
|
activity_type: place.type
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
checkPointInRegion();
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{place.display_name}
|
{place.display_name}
|
||||||
|
@ -668,13 +610,117 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
||||||
{/each}
|
{/each}
|
||||||
</MapLibre>
|
</MapLibre>
|
||||||
</div>
|
</div>
|
||||||
{#if region_name}
|
|
||||||
<p class="text-lg font-semibold mt-2">Region: {region_name} ({region_id})</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ---OLD--- -->
|
<div class="collapse collapse-plus bg-base-200 mb-4 overflow-visible">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Activity Types ({adventure.activity_types?.length || 0})
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="activity_types"
|
||||||
|
name="activity_types"
|
||||||
|
hidden
|
||||||
|
bind:value={adventure.activity_types}
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
/>
|
||||||
|
<ActivityComplete bind:activities={adventure.activity_types} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="collapse collapse-plus bg-base-200 mb-4">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Visits ({adventure.visits.length})
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<label class="label cursor-pointer flex items-start space-x-2">
|
||||||
|
<span class="label-text">Constrain to collection dates</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-primary"
|
||||||
|
id="is_public"
|
||||||
|
name="is_public"
|
||||||
|
/>
|
||||||
|
<!-- TODO: implement this constrain -->
|
||||||
|
</label>
|
||||||
|
<div class="flex gap-2 mb-1">
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
placeholder="Start Date"
|
||||||
|
bind:value={new_start_date}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
addNewVisit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
placeholder="End Date"
|
||||||
|
bind:value={new_end_date}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
addNewVisit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 mb-1">
|
||||||
|
<!-- textarea for notes -->
|
||||||
|
<textarea
|
||||||
|
class="textarea textarea-bordered w-full"
|
||||||
|
placeholder="Add notes"
|
||||||
|
bind:value={new_notes}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
addNewVisit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button type="button" class="btn btn-neutral" on:click={addNewVisit}>Add</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if adventure.visits.length > 0}
|
||||||
|
<h2 class=" font-bold text-xl mt-2">My Visits</h2>
|
||||||
|
{#each adventure.visits as visit}
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<p>
|
||||||
|
{new Date(visit.start_date).toLocaleDateString(undefined, {
|
||||||
|
timeZone: 'UTC'
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{new Date(visit.end_date).toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
||||||
|
</p>
|
||||||
|
<p>{visit.notes}</p>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm btn-error mb-1"
|
||||||
|
on:click={() => {
|
||||||
|
adventure.visits = adventure.visits.filter((v) => v !== visit);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
|
@ -798,29 +844,3 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<!-- {#each adventure.visits as visit}
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<p>
|
|
||||||
{new Date(visit.start_date).toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
|
||||||
{#if visit.end_date}
|
|
||||||
- {new Date(visit.end_date).toLocaleDateString(undefined, {
|
|
||||||
timeZone: 'UTC'
|
|
||||||
})}
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-sm btn-error absolute right-0 mt-2.5 mr-4"
|
|
||||||
on:click={() => {
|
|
||||||
adventure.visits = adventure.visits.filter((v) => v.id !== visit.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{#if adventure.visits.length == 0}
|
|
||||||
<p>No visits</p>
|
|
||||||
{/if} -->
|
|
||||||
|
|
|
@ -66,29 +66,31 @@ export function groupAdventuresByDate(
|
||||||
}
|
}
|
||||||
|
|
||||||
adventures.forEach((adventure) => {
|
adventures.forEach((adventure) => {
|
||||||
if (adventure.date) {
|
adventure.visits.forEach((visit) => {
|
||||||
const adventureDate = new Date(adventure.date).toISOString().split('T')[0];
|
if (visit.start_date) {
|
||||||
if (adventure.end_date) {
|
const adventureDate = new Date(visit.start_date).toISOString().split('T')[0];
|
||||||
const endDate = new Date(adventure.end_date).toISOString().split('T')[0];
|
if (visit.end_date) {
|
||||||
|
const endDate = new Date(visit.end_date).toISOString().split('T')[0];
|
||||||
|
|
||||||
// Loop through all days and include adventure if it falls within the range
|
// Loop through all days and include adventure if it falls within the range
|
||||||
for (let i = 0; i < numberOfDays; i++) {
|
for (let i = 0; i < numberOfDays; i++) {
|
||||||
const currentDate = new Date(startDate);
|
const currentDate = new Date(startDate);
|
||||||
currentDate.setUTCDate(startDate.getUTCDate() + i);
|
currentDate.setUTCDate(startDate.getUTCDate() + i);
|
||||||
const dateString = currentDate.toISOString().split('T')[0];
|
const dateString = currentDate.toISOString().split('T')[0];
|
||||||
|
|
||||||
// Include the current day if it falls within the adventure date range
|
// Include the current day if it falls within the adventure date range
|
||||||
if (dateString >= adventureDate && dateString <= endDate) {
|
if (dateString >= adventureDate && dateString <= endDate) {
|
||||||
if (groupedAdventures[dateString]) {
|
if (groupedAdventures[dateString]) {
|
||||||
groupedAdventures[dateString].push(adventure);
|
groupedAdventures[dateString].push(adventure);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (groupedAdventures[adventureDate]) {
|
||||||
|
// If there's no end date, add adventure to the start date only
|
||||||
|
groupedAdventures[adventureDate].push(adventure);
|
||||||
}
|
}
|
||||||
} else if (groupedAdventures[adventureDate]) {
|
|
||||||
// If there's no end date, add adventure to the start date only
|
|
||||||
groupedAdventures[adventureDate].push(adventure);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return groupedAdventures;
|
return groupedAdventures;
|
||||||
|
|
|
@ -185,10 +185,7 @@
|
||||||
{adventureToEdit}
|
{adventureToEdit}
|
||||||
on:close={() => (isAdventureModalOpen = false)}
|
on:close={() => (isAdventureModalOpen = false)}
|
||||||
on:save={saveOrCreate}
|
on:save={saveOrCreate}
|
||||||
collection_id={collection.id}
|
{collection}
|
||||||
startDate={collection.start_date}
|
|
||||||
endDate={collection.end_date}
|
|
||||||
is_collection={true}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -598,11 +595,11 @@
|
||||||
<p class="font-semibold text-black text-md">
|
<p class="font-semibold text-black text-md">
|
||||||
{adventure.type.charAt(0).toUpperCase() + adventure.type.slice(1)}
|
{adventure.type.charAt(0).toUpperCase() + adventure.type.slice(1)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<!-- <p>
|
||||||
{adventure.date
|
{adventure.
|
||||||
? new Date(adventure.date).toLocaleDateString(undefined, { timeZone: 'UTC' })
|
? new Date(adventure.date).toLocaleDateString(undefined, { timeZone: 'UTC' })
|
||||||
: ''}
|
: ''}
|
||||||
</p>
|
</p> -->
|
||||||
</Popup>
|
</Popup>
|
||||||
</DefaultMarker>
|
</DefaultMarker>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue