diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-06-01 22:10:32 +0200 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-06-01 22:10:32 +0200 |
| commit | a640703f2da8815dc26ad1600a6f206be1624379 (patch) | |
| tree | dbda195fb5783d16487e557e06471cf848b75427 /apps/projects-web/src/app/pages/views/entry-form/index.svelte | |
| download | greatoffice-a640703f2da8815dc26ad1600a6f206be1624379.tar.xz greatoffice-a640703f2da8815dc26ad1600a6f206be1624379.zip | |
feat: Initial after clean slate
Diffstat (limited to 'apps/projects-web/src/app/pages/views/entry-form/index.svelte')
| -rw-r--r-- | apps/projects-web/src/app/pages/views/entry-form/index.svelte | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/apps/projects-web/src/app/pages/views/entry-form/index.svelte b/apps/projects-web/src/app/pages/views/entry-form/index.svelte new file mode 100644 index 0000000..cb974ed --- /dev/null +++ b/apps/projects-web/src/app/pages/views/entry-form/index.svelte @@ -0,0 +1,196 @@ +<script lang="ts"> + import {TimeEntryDto} from "$shared/lib/models/TimeEntryDto"; + import {Temporal} from "@js-temporal/polyfill"; + import {createEventDispatcher, onMount, onDestroy} from "svelte"; + import DateTimePart from "./sections/date-time.svelte"; + import LabelsPart from "./sections/labels.svelte"; + import CategoryPart from "./sections/category.svelte"; + import Button from "$shared/components/button.svelte"; + import {Textarea} from "$shared/components/form"; + import Alert from "$shared/components/alert.svelte"; + import {is_guid} from "$shared/lib/helpers"; + import {create_entry_async, edit_entry_async} from "$app/lib/stores/entries"; + + const dispatch = createEventDispatcher(); + + let formError = ""; + let formIsLoading = false; + let description = ""; + let descriptionError = ""; + let dateTimePart; + let labelsPart; + let categoryPart; + let entryId; + + onMount(() => { + formIsLoading = true; + + Promise.all([categoryPart.load_categories(), labelsPart.load_labels()]).then(() => { + formIsLoading = false; + }); + + window.addEventListener("keydown", handle_window_keydown); + }); + + onDestroy(() => { + window.removeEventListener("keydown", handle_window_keydown); + }); + + function handle_window_keydown(event) { + if (event.ctrlKey && event.code === "Enter") { + submit_form(); + } + } + + function validate_form() { + return dateTimePart.is_valid() && categoryPart.is_valid() && description_is_valid(); + } + + function description_is_valid() { + if (!description) { + descriptionError = "Description is required"; + } else { + descriptionError = ""; + } + + return description; + } + + function get_payload() { + const response = {} as TimeEntryDto; + const values = get_values(); + if (!is_guid(values.id)) { + delete values.id; + } else { + response.id = values.id; + } + + const currentTimeZone = Temporal.Now.zonedDateTimeISO().offset; + response.start = values.date + "T" + values.fromTimeValue + currentTimeZone.toString(); + response.stop = values.date + "T" + values.toTimeValue + currentTimeZone.toString(); + + response.category = { + id: values.category.id, + }; + + const selectedLabels = values.labels; + if (selectedLabels?.length > 0 ?? false) { + response.labels = selectedLabels; + } + + const descriptionContent = description?.trim(); + if (descriptionContent?.length > 0 ?? false) { + response.description = descriptionContent; + } + + return response; + } + + async function submit_form() { + formError = ""; + if (validate_form()) { + const payload = get_payload() as TimeEntryDto; + formIsLoading = true; + if (is_guid(payload.id)) { + const response = await edit_entry_async(payload); + if (response.ok) { + functions.reset(); + dispatch("updated", response.data); + } else { + formError = "An error occured while updating the entry, try again soon"; + formIsLoading = false; + } + } else { + const response = await create_entry_async(payload); + if (response.ok) { + functions.reset(); + dispatch("created"); + } else { + formError = "An error occured while creating the entry, try again soon"; + formIsLoading = false; + } + } + } + } + + function get_values() { + return { + id: entryId, + toTimeValue: dateTimePart.get_to_time_value(), + fromTimeValue: dateTimePart.get_from_time_value(), + date: dateTimePart.get_date(), + category: categoryPart.get_selected(), + labels: labelsPart.get_selected(), + description: description, + }; + } + + export const functions = { + set_values(values) { + entryId = values.id; + dateTimePart.set_values(values); + labelsPart.select_labels(values?.labels.map((c) => c.id) ?? []); + categoryPart.select_category(values?.category?.id); + description = values.description; + }, + set_time(value: {to: Temporal.PlainTime, from: Temporal.PlainTime}) { + dateTimePart.set_times(value); + }, + set_description(value: string) { + if (description) description = description + "\n\n" + value; + else description = value; + }, + reset() { + formIsLoading = false; + entryId = ""; + labelsPart.reset(); + categoryPart.reset(); + dateTimePart.reset(true); + description = ""; + formError = ""; + }, + }; +</script> + +<form on:submit|preventDefault={submit_form} + on:reset={() => functions.reset()}> + <div class="margin-y-sm"> + <Alert visible={formError !== ""} + message={formError} + type="error"/> + </div> + + <div class="margin-bottom-sm"> + <DateTimePart bind:functions={dateTimePart}/> + </div> + + <div class="margin-bottom-sm"> + <CategoryPart bind:functions={categoryPart}/> + </div> + + <div class="margin-bottom-sm"> + <LabelsPart bind:functions={labelsPart}/> + </div> + + <div class="margin-bottom-sm"> + <Textarea class="width-100%" + id="description" + label="Description" + errorText="{descriptionError}" + bind:value={description}></Textarea> + </div> + + <div class="flex flex-row justify-end gap-x-xs"> + {#if entryId} + <Button text="Reset" + on:click={() => functions.reset()} + variant="subtle" + /> + {/if} + <Button loading={formIsLoading} + type="submit" + variant="primary" + text={entryId ? "Save" : "Create"} + /> + </div> +</form> |
