finishes trail create
This commit is contained in:
31
web/src/lib/components/base/button.svelte
Normal file
31
web/src/lib/components/base/button.svelte
Normal file
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
export let type: "button" | "submit" | "reset" | null | undefined =
|
||||
undefined;
|
||||
export let icon: string = "";
|
||||
export let extraClasses: string = "";
|
||||
export let primary: boolean = false;
|
||||
export let secondary: boolean = false;
|
||||
export let large: boolean = false;
|
||||
export let loading: boolean = false;
|
||||
export let disabled: boolean = false;
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={extraClasses}
|
||||
class:btn-primary={primary}
|
||||
class:btn-secondary={secondary}
|
||||
class:btn-large={large}
|
||||
class:btn-disabled={disabled || loading}
|
||||
disabled={disabled || loading}
|
||||
on:click
|
||||
{type}
|
||||
>
|
||||
{#if !loading}
|
||||
{#if icon}
|
||||
<i class="fa fa-{icon} mr-2"></i>
|
||||
{/if}
|
||||
<slot />
|
||||
{:else}
|
||||
<div class="spinner"></div>
|
||||
{/if}
|
||||
</button>
|
||||
29
web/src/lib/components/base/datepicker.svelte
Normal file
29
web/src/lib/components/base/datepicker.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
export let name: string = "";
|
||||
export let value: string | number | Date = "";
|
||||
export let label: string = "";
|
||||
export let error: string = "";
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if label.length}
|
||||
<p class="text-sm font-medium pb-1">
|
||||
{label}
|
||||
</p>
|
||||
{/if}
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
{name}
|
||||
class="bg-gray-50 border rounded-md p-3 transition-colors focus:border-primary focus:outline-none focus:ring-0 w-full"
|
||||
class:border-red-400={error.length > 0}
|
||||
class:bg-red-50={error.length > 0}
|
||||
type="date"
|
||||
bind:value
|
||||
on:change
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span class="textfield-error text-xs text-red-400">
|
||||
{error}
|
||||
</span>
|
||||
</div>
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
let isOpen = false;
|
||||
|
||||
function toggleMenu() {
|
||||
function toggleMenu(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
isOpen = !isOpen;
|
||||
}
|
||||
|
||||
@@ -15,8 +17,8 @@
|
||||
isOpen = false;
|
||||
}
|
||||
|
||||
function handleItemClick(item: { text: string, value: any }) {
|
||||
dispatch("change", item);
|
||||
function handleItemClick(item: { text: string; value: any }) {
|
||||
dispatch("change", item);
|
||||
closeMenu();
|
||||
}
|
||||
</script>
|
||||
@@ -27,6 +29,7 @@
|
||||
<button
|
||||
class="hover:bg-gray-100 w-8 h-8 rounded-full"
|
||||
on:click={toggleMenu}
|
||||
type="button"
|
||||
>
|
||||
<i class="fa fa-ellipsis-vertical"></i>
|
||||
</button>
|
||||
@@ -42,8 +45,8 @@
|
||||
<li
|
||||
class="menu-item p-4 cursor-pointer hover:bg-gray-100 focus:bg-gray-200 transition-colors"
|
||||
tabindex="0"
|
||||
on:mouseup={() => handleItemClick(item)}
|
||||
on:keydown={() => handleItemClick(item)}
|
||||
on:mouseup|stopPropagation={() => handleItemClick(item)}
|
||||
on:keydown|stopPropagation={() => handleItemClick(item)}
|
||||
>
|
||||
{item.text}
|
||||
</li>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
export let name: string = "";
|
||||
export let value: any;
|
||||
export let items: { text: string; value: any }[] = [];
|
||||
export let label: string = "";
|
||||
@@ -11,6 +12,7 @@
|
||||
</p>
|
||||
{/if}
|
||||
<select
|
||||
{name}
|
||||
class="bg-gray-50 h-10 w-full px-4 border-r-8 border-transparent outline outline-1 outline-gray-200 rounded-md focus:outline-primary transition-colors"
|
||||
bind:value
|
||||
>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
export let name: string = "";
|
||||
export let value: string | number = "";
|
||||
export let placeholder: string = "";
|
||||
export let label: string = "";
|
||||
@@ -17,6 +18,7 @@
|
||||
<i class="fa fa-{icon}"></i>
|
||||
{/if}
|
||||
<input
|
||||
{name}
|
||||
class="bg-gray-50 border rounded-md p-3 transition-colors focus:border-primary focus:outline-none focus:ring-0 w-full"
|
||||
class:border-red-400={error.length > 0}
|
||||
class:bg-red-50={error.length > 0}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
export let name: string = "";
|
||||
export let value: string | number = "";
|
||||
export let placeholder: string = "";
|
||||
export let rows: number = 3;
|
||||
@@ -13,6 +14,7 @@
|
||||
</p>
|
||||
{/if}
|
||||
<textarea
|
||||
{name}
|
||||
class="bg-gray-50 border rounded-md p-3 resize-none transition-colors focus:border-primary focus:outline-none focus:ring-0 w-full"
|
||||
{rows}
|
||||
{placeholder}
|
||||
|
||||
44
web/src/lib/components/photo_card.svelte
Normal file
44
web/src/lib/components/photo_card.svelte
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let src: string;
|
||||
export let isThumbnail: boolean = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function handleThumbnailClick() {
|
||||
dispatch("thumbnail", src);
|
||||
}
|
||||
|
||||
function handleDeleteClick() {
|
||||
dispatch("delete", src);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="group relative h-32 w-32 rounded-xl bg-cover bg-no-repeat"
|
||||
style="background-image: url({src});"
|
||||
>
|
||||
{#if isThumbnail}
|
||||
<i
|
||||
class="fa fa-file-image absolute top-2 right-2 text-primary bg-white rounded-full px-[10px] py-2 shadow-lg"
|
||||
></i>
|
||||
{/if}
|
||||
<div
|
||||
class="flex opacity-0 group-hover:opacity-100 absolute top-0 w-full h-full bg-white bg-opacity-75 items-center justify-center gap-6 transition-all"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="tooltip"
|
||||
data-title="Make thumbnail"
|
||||
on:click={handleThumbnailClick}
|
||||
><i class="fa fa-file-image text-primary"></i></button
|
||||
>
|
||||
<button
|
||||
class="tooltip"
|
||||
data-title="Delete"
|
||||
on:click={handleDeleteClick}
|
||||
type="button"><i class="fa fa-trash text-red-500"></i></button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
24
web/src/lib/components/summit_log/summit_log_card.svelte
Normal file
24
web/src/lib/components/summit_log/summit_log_card.svelte
Normal file
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import type { SummitLog } from "$lib/models/summit_log";
|
||||
import { format, parse } from "date-fns";
|
||||
import Dropdown from "../base/dropdown.svelte";
|
||||
|
||||
export let log: SummitLog;
|
||||
export let mode: "show" | "edit" = "show";
|
||||
|
||||
const dropdownItems = [
|
||||
{ text: "Edit", value: "edit" },
|
||||
{ text: "Delete", value: "delete" },
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="p-4 my-2 border rounded-xl">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h5 class="font-medium mr-2">{format(log.date, 'dd.MM.yyyy')}</h5>
|
||||
|
||||
{#if mode == "edit"}
|
||||
<Dropdown items={dropdownItems} on:change></Dropdown>
|
||||
{/if}
|
||||
</div>
|
||||
<span>{log.text}</span>
|
||||
</div>
|
||||
67
web/src/lib/components/summit_log/summit_log_modal.svelte
Normal file
67
web/src/lib/components/summit_log/summit_log_modal.svelte
Normal file
@@ -0,0 +1,67 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import { summitLogSchema, type SummitLog } from "$lib/models/summit_log";
|
||||
import { summitLog } from "$lib/stores/summit_log_store";
|
||||
import { createForm } from "$lib/vendor/svelte-form-lib/index";
|
||||
import Datepicker from "../base/datepicker.svelte";
|
||||
import Modal from "../base/modal.svelte";
|
||||
import TextField from "../base/text_field.svelte";
|
||||
import { util } from "$lib/vendor/svelte-form-lib/util";
|
||||
|
||||
export let openModal: (() => void) | undefined = undefined;
|
||||
export let closeModal: (() => void) | undefined = undefined;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const { form, errors, handleChange, handleSubmit } = createForm<SummitLog>({
|
||||
initialValues: $summitLog,
|
||||
validationSchema: summitLogSchema,
|
||||
onSubmit: async (submittedValues) => {
|
||||
dispatch("save", submittedValues);
|
||||
closeModal!();
|
||||
},
|
||||
});
|
||||
$: form.set(util.cloneDeep($summitLog));
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
id="summit-log-modal"
|
||||
title="Add Entry"
|
||||
let:openModal
|
||||
bind:openModal
|
||||
bind:closeModal
|
||||
>
|
||||
<slot {openModal} />
|
||||
<form
|
||||
id="summit-log-form"
|
||||
slot="content"
|
||||
class="modal-content space-y-4"
|
||||
on:submit={handleSubmit}
|
||||
>
|
||||
<div class="flex gap-4">
|
||||
<Datepicker
|
||||
name="date"
|
||||
label="Date"
|
||||
bind:value={$form.date}
|
||||
error={$errors.date}
|
||||
on:change={handleChange}
|
||||
></Datepicker>
|
||||
<div class="basis-full">
|
||||
<TextField
|
||||
name="text"
|
||||
label="Text"
|
||||
bind:value={$form.text}
|
||||
error={$errors.text}
|
||||
on:change={handleChange}
|
||||
></TextField>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="footer" class="flex items-center gap-4">
|
||||
<button class="btn-secondary" on:click={closeModal}>Cancel</button>
|
||||
<button class="btn-primary" type="submit" form="summit-log-form"
|
||||
>Save</button
|
||||
>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -1,11 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { SummitLog } from "$lib/models/summit_log";
|
||||
import { formatISODate } from "$lib/util/format_util";
|
||||
|
||||
export let log: SummitLog;
|
||||
</script>
|
||||
|
||||
<div class="p-4 my-2 border rounded-xl">
|
||||
<h5 class="font-medium mr-2">{formatISODate(log.date)}</h5>
|
||||
<span>{log.text}</span>
|
||||
</div>
|
||||
@@ -2,17 +2,26 @@
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
import { formatMeters, formatTimeHHMM } from "$lib/util/format_util";
|
||||
import Dropdown from "../base/dropdown.svelte";
|
||||
|
||||
export let trail: Trail;
|
||||
|
||||
const dropdownItems = [
|
||||
{ text: "Edit", value: "edit" },
|
||||
{ text: "Delete", value: "delete" },
|
||||
];
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="trail-card rounded-2xl shadow-md w-72 overflow-hidden cursor-pointer"
|
||||
>
|
||||
<img class="w-full h-48" src={getFileURL(trail, trail.thumbnail)} alt="" />
|
||||
<div class="trail-card rounded-2xl shadow-md w-72 cursor-pointer">
|
||||
<div class="w-full h-48 overflow-hidden rounded-t-2xl">
|
||||
<img src={getFileURL(trail, trail.thumbnail)} alt="" />
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div>
|
||||
<h4 class="font-semibold text-lg">{trail.name}</h4>
|
||||
<div class="flex justify-between items-center">
|
||||
<h4 class="font-semibold text-lg">{trail.name}</h4>
|
||||
<Dropdown on:change items={dropdownItems}></Dropdown>
|
||||
</div>
|
||||
<h5><i class="fa fa-location-dot mr-3"></i>{trail.location}</h5>
|
||||
</div>
|
||||
<div class="flex mt-2 gap-4 text-sm text-gray-500">
|
||||
@@ -2,11 +2,12 @@
|
||||
import { Waypoint, waypointSchema } from "$lib/models/waypoint";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import { createForm } from "svelte-forms-lib";
|
||||
import { createForm } from "$lib/vendor/svelte-form-lib/index";
|
||||
import Modal from "../base/modal.svelte";
|
||||
import TextField from "../base/text_field.svelte";
|
||||
import Textarea from "../base/textarea.svelte";
|
||||
import { waypoint } from "$lib/stores/waypoint_store";
|
||||
import { util } from "$lib/vendor/svelte-form-lib/util";
|
||||
|
||||
export let openModal: (() => void) | undefined = undefined;
|
||||
export let closeModal: (() => void) | undefined = undefined;
|
||||
@@ -21,7 +22,7 @@
|
||||
closeModal!();
|
||||
},
|
||||
});
|
||||
$: form.set($waypoint);
|
||||
$: form.set(util.cloneDeep($waypoint));
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
@@ -41,6 +42,7 @@
|
||||
<div class="flex gap-4">
|
||||
<div class="basis-full">
|
||||
<TextField
|
||||
name="name"
|
||||
label="Name"
|
||||
bind:value={$form.name}
|
||||
error={$errors.name}
|
||||
@@ -49,6 +51,7 @@
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
name="icon"
|
||||
label="Icon"
|
||||
bind:value={$form.icon}
|
||||
icon={$form.icon}
|
||||
@@ -58,6 +61,7 @@
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
name="description"
|
||||
label="Description"
|
||||
bind:value={$form.description}
|
||||
error={$errors.description}
|
||||
@@ -65,12 +69,14 @@
|
||||
></Textarea>
|
||||
<div class="flex gap-4">
|
||||
<TextField
|
||||
name="lat"
|
||||
label="Latitude"
|
||||
bind:value={$form.lat}
|
||||
error={$errors.lat}
|
||||
on:change={handleChange}
|
||||
></TextField>
|
||||
<TextField
|
||||
name="lon"
|
||||
label="Longitude"
|
||||
bind:value={$form.lon}
|
||||
error={$errors.lat}
|
||||
|
||||
Reference in New Issue
Block a user