diff options
Diffstat (limited to 'apps/projects-web/src/app/pages/views/entry-form/sections')
3 files changed, 305 insertions, 0 deletions
diff --git a/apps/projects-web/src/app/pages/views/entry-form/sections/category.svelte b/apps/projects-web/src/app/pages/views/entry-form/sections/category.svelte new file mode 100644 index 0000000..f98c045 --- /dev/null +++ b/apps/projects-web/src/app/pages/views/entry-form/sections/category.svelte @@ -0,0 +1,75 @@ +<script> + import {generate_random_hex_color} from "$shared/lib/colors"; + import Dropdown from "$shared/components/dropdown.svelte"; + import {is_guid, move_focus} from "$shared/lib/helpers"; + import categories, {reload_categories, create_category_async} from "$app/lib/stores/categories"; + + let categoriesError = ""; + let loading = false; + + let DropdownExports; + + function reset() { + DropdownExports.reset(); + categoriesError = ""; + console.log("Reset category-part"); + } + + async function on_create({name}) { + loading = true; + const response = await create_category_async({ + name: name, + color: generate_random_hex_color(), + }); + loading = false; + if (response.ok) { + // Small pause to allow loading state to update everywhere. + setTimeout(() => select_category(response.data.id), 50); + } + } + + function get_selected() { + return $categories.find((c) => c.selected === true); + } + + function select_category(id) { + DropdownExports.select(id); + } + + function is_valid() { + let isValid = true; + const category = get_selected(); + if (!is_guid(category?.id)) { + categoriesError = "Category is required"; + isValid = false; + move_focus(document.getElementById("category-dropdown")); + } else { + categoriesError = ""; + } + return isValid; + } + + export const functions = { + get_selected, + reset, + is_valid, + select_category, + load_categories: reload_categories, + }; +</script> + +<Dropdown + entries={$categories} + label="Category" + maxlength="50" + createable={true} + placeholder="Search or create" + id="category-dropdown" + loading={loading} + name="category-dropdown" + on_create_async={on_create} + noResultsText="No categories available (Create a new one by searching for it and pressing enter)" + errorText="{categoriesError}" + bind:this={DropdownExports} +/> + diff --git a/apps/projects-web/src/app/pages/views/entry-form/sections/date-time.svelte b/apps/projects-web/src/app/pages/views/entry-form/sections/date-time.svelte new file mode 100644 index 0000000..c91e014 --- /dev/null +++ b/apps/projects-web/src/app/pages/views/entry-form/sections/date-time.svelte @@ -0,0 +1,165 @@ +<script lang="ts"> + import {Temporal} from "@js-temporal/polyfill"; + + // TIME + let fromTimeValue = ""; + let fromTimeError = ""; + let toTimeValue = ""; + let toTimeError = ""; + + function handle_from_time_changed(e) { + fromTimeValue = e.target.value; + if (fromTimeValue) { + fromTimeError = ""; + } + } + + function handle_to_time_changed(e) { + toTimeValue = e.target.value; + if (toTimeValue) { + toTimeError = ""; + } + } + + // DATE + let date = Temporal.Now.plainDateTimeISO().toString().substring(0, 10); + let dateError = ""; + + function is_valid() { + let isValid = true; + let focusIsSet = false; + if (!date) { + dateError = "Date is required"; + isValid = false; + if (!focusIsSet) { + document.getElementById("date")?.focus(); + focusIsSet = true; + } + } else { + dateError = ""; + } + + if (!fromTimeValue) { + fromTimeError = "From is required"; + isValid = false; + if (!focusIsSet) { + document.getElementById("from")?.focus(); + focusIsSet = true; + } + } else if (toTimeValue && fromTimeValue > toTimeValue) { + fromTimeError = "From can not be after To"; + isValid = false; + if (!focusIsSet) { + document.getElementById("from")?.focus(); + focusIsSet = true; + } + } else if (fromTimeValue === toTimeValue) { + fromTimeError = "From and To can not be equal"; + isValid = false; + if (!focusIsSet) { + document.getElementById("from")?.focus(); + focusIsSet = true; + } + } else { + fromTimeError = ""; + } + + if (!toTimeValue) { + toTimeError = "To is required"; + isValid = false; + if (!focusIsSet) { + document.getElementById("to")?.focus(); + focusIsSet = true; + } + } else if (fromTimeValue && toTimeValue < fromTimeValue) { + toTimeError = "To can not be before From"; + isValid = false; + if (!focusIsSet) { + document.getElementById("to")?.focus(); + focusIsSet = true; + } + } else { + toTimeError = ""; + } + + return isValid; + } + + export const functions = { + get_from_time_value() { + return fromTimeValue; + }, + get_to_time_value() { + return toTimeValue; + }, + get_date() { + return date; + }, + is_valid, + reset(focusDate = false) { + fromTimeValue = ""; + toTimeValue = ""; + if (focusDate) { + document.getElementById("date")?.focus(); + } + }, + set_times(value) { + console.log(value); + fromTimeValue = value.from.toString().substring(0, 5); + toTimeValue = value.to.toString().substring(0, 5); + }, + set_date(new_date: Temporal.PlainDate) { + date = new_date.toString(); + }, + set_values(values) { + const currentTimeZone = Temporal.Now.timeZone().id; + const startDate = Temporal.Instant.from(values.start); + const stopDate = Temporal.Instant.from(values.stop); + fromTimeValue = startDate.toZonedDateTimeISO(currentTimeZone).toPlainTime().toString().substring(0, 5); + toTimeValue = stopDate.toZonedDateTimeISO(currentTimeZone).toPlainTime().toString().substring(0, 5); + date = startDate.toZonedDateTimeISO(currentTimeZone).toPlainDate().toString(); + } + }; +</script> + +<div class="grid gap-xs"> + <div class="col-4"> + <label for="date" + class="form-label margin-bottom-xxs">Date</label> + <input type="date" + id="date" + class="form-control width-100%" + bind:value={date}> + {#if dateError} + <small class="color-error">{dateError}</small> + {/if} + </div> + <div class="col-4"> + <label for="from" + class="form-label margin-bottom-xxs">From</label> + <input id="from" + class="form-control width-100%" + pattern="[0-9][0-9]:[0-9][0-9]" + type="time" + bind:value={fromTimeValue} + on:input={handle_from_time_changed} + /> + {#if fromTimeError} + <small class="color-error">{fromTimeError}</small> + {/if} + </div> + <div class="col-4"> + <label for="to" + class="form-label margin-bottom-xxs">To</label> + <input id="to" + class="form-control width-100%" + pattern="[0-9][0-9]:[0-9][0-9]" + type="time" + bind:value={toTimeValue} + on:input={handle_to_time_changed} + /> + {#if toTimeError} + <small class="color-error">{toTimeError}</small> + {/if} + </div> +</div> diff --git a/apps/projects-web/src/app/pages/views/entry-form/sections/labels.svelte b/apps/projects-web/src/app/pages/views/entry-form/sections/labels.svelte new file mode 100644 index 0000000..06c703d --- /dev/null +++ b/apps/projects-web/src/app/pages/views/entry-form/sections/labels.svelte @@ -0,0 +1,65 @@ +<script> + import {generate_random_hex_color} from "$shared/lib/colors"; + import labels, {reload_labels, create_label_async} from "$app/lib/stores/labels"; + import Dropdown from "$shared/components/dropdown.svelte"; + + let labelsError = ""; + let loading = false; + let DropdownExports; + + function reset() { + DropdownExports.reset(); + console.log("Reset labels-part"); + } + + function get_selected() { + return $labels.filter((c) => Object.hasOwn(c, "selected") && c.selected === true); + } + + function select_label(id) { + DropdownExports.select(id); + } + + function select_labels(ids) { + for (const id of ids) { + DropdownExports.select(id); + } + } + + async function on_create({name}) { + loading = true; + const response = await create_label_async({ + name: name, + color: generate_random_hex_color(), + }); + loading = false; + if (response.ok) { + // Small pause to allow loading state to update everywhere. + setTimeout(() => select_label(response.data.id), 50); + } + } + + export const functions = { + get_selected, + reset, + load_labels: reload_labels, + select_labels, + select_label, + }; +</script> + +<Dropdown + entries={$labels} + label="Labels" + maxlength="50" + createable={true} + placeholder="Search or create" + multiple="{true}" + id="labels-search" + name="labels-search" + on_create_async={on_create} + noResultsText="No labels available (Create a new one by searching for it and pressing enter)" + errorText="{labelsError}" + bind:this={DropdownExports} + {loading} +/> |
