diff options
Diffstat (limited to 'apps/projects-web/src/app/pages/views')
9 files changed, 0 insertions, 1147 deletions
diff --git a/apps/projects-web/src/app/pages/views/category-form/index.svelte b/apps/projects-web/src/app/pages/views/category-form/index.svelte deleted file mode 100644 index e8c0f94..0000000 --- a/apps/projects-web/src/app/pages/views/category-form/index.svelte +++ /dev/null @@ -1,144 +0,0 @@ -<script lang="ts"> - import Alert from "$shared/components/alert.svelte"; - import Dropdown from "$shared/components/dropdown.svelte"; - import labels, {reload_labels, create_label_async} from "$app/lib/stores/labels"; - import {generate_random_hex_color} from "$shared/lib/colors"; - import {get} from "svelte/store"; - - let LabelsDropdown; - - const dough = { - error: "", - fields: { - name: { - value: "", - error: "", - validate() { - return false; - } - }, - color: { - value: "", - error: "", - validate() { - return true; - } - }, - labels: { - loading: false, - value: [], - error: "", - validate() { - return true; - }, - async create({name}) { - dough.fields.labels.loading = true; - const response = await create_label_async({ - name: name, - color: generate_random_hex_color(), - }); - dough.fields.labels.loading = false; - if (response.ok) { - // Small pause to allow loading state to update everywhere. - setTimeout(() => LabelsDropdown.select_entry(response.data.id), 50); - } - } - }, - archived: { - value: false, - error: "", - validate() { - return true; - } - } - }, - bake() { - // labels.filter((c) => Object.hasOwn(c, "selected") && c.selected === true); - return { - labels: dough.fields.labels.value, - name: dough.fields.name.value, - color: dough.fields.color.value, - }; - }, - submit(event) { - const bread = dough.bake(); - console.log(bread); - console.log("Submitted"); - } - }; - - const functions = { - set(values) { - functions.set_archived(values.archived); - functions.set_labels(values.labels); - functions.set_color(values.color); - functions.set_name(values.name); - }, - is_valid() { - let isValid = true; - if (!dough.fields.labels.validate()) isValid = false; - if (!dough.fields.color.validate()) isValid = false; - if (!dough.fields.name.validate()) isValid = false; - if (!dough.fields.archived.validate()) isValid = false; - return isValid; - }, - set_archived(value) { - dough.fields.archived.value = value; - }, - set_labels(value) { - dough.fields.labels.value = value; - }, - set_color(value) { - dough.fields.color.value = value; - }, - set_name(value) { - dough.fields.name.value = value; - }, - }; -</script> - -<form on:submit|preventDefault={dough.submit}> - <div class="margin-y-sm"> - <Alert visible={dough.error !== ""} - message={dough.error} - type="error"/> - </div> - <div class="grid gap-x-xs margin-bottom-sm"> - <div class="col-10"> - <label for="name" - class="form-label margin-bottom-xxs">Name</label> - <input type="text" - class="form-control width-100%" - id="name" - bind:value={dough.fields.name.value}/> - {#if dough.fields.name.error} - <small class="color-error">{dough.fields.name.error}</small> - {/if} - </div> - <div class="col-2"> - <label for="color" - class="form-label margin-bottom-xxs">Color</label> - <input type="color" - class="form-control width-100%" - id="color" - style="height: 41px" - bind:value={dough.fields.color.value}/> - {#if dough.fields.color.error} - <small class="color-error">{dough.fields.color.error}</small> - {/if} - </div> - </div> - <div class="margin-bottom-sm"> - <label for="labels" - class="form-label margin-bottom-xxs">Default labels</label> - <Dropdown id="labels" - createable={true} - placeholder="Search or create" - entries={$labels} - multiple={true} - on_create_async={(name) => dough.fields.labels.create({name})}/> - {#if dough.fields.labels.error} - <small class="color-error">{dough.fields.labels.error}</small> - {/if} - </div> -</form> diff --git a/apps/projects-web/src/app/pages/views/data-table-paginator.svelte b/apps/projects-web/src/app/pages/views/data-table-paginator.svelte deleted file mode 100644 index 7696ca2..0000000 --- a/apps/projects-web/src/app/pages/views/data-table-paginator.svelte +++ /dev/null @@ -1,107 +0,0 @@ -<script> - import {createEventDispatcher, onMount} from "svelte"; - import {restrict_input_to_numbers} from "$shared/lib/helpers"; - - const dispatch = createEventDispatcher(); - export let page = 1; - export let pageCount = 1; - let prevCount = page; - let canIncrement = false; - let canDecrement = false; - $: canIncrement = page < pageCount; - $: canDecrement = page > 1; - - onMount(() => { - restrict_input_to_numbers(document.querySelector("#curr-page")); - }); - - function increment() { - if (canIncrement) { - page++; - } - } - - function decrement() { - if (canDecrement) { - page--; - } - } - - $: if (page) { - handle_change(); - } - - function handle_change() { - if (page === prevCount) { - return; - } - prevCount = page; - if (page > pageCount) { - page = pageCount; - } - dispatch("value_change", { - newValue: page, - }); - } -</script> - -<nav class="pagination" - aria-label="Pagination"> - <ul - class="pagination__list flex flex-wrap gap-xxxs justify-center justify-end@md" - > - <li> - <button - on:click={decrement} - class="reset pagination__item {canDecrement ? '' : 'c-disabled'}" - > - <svg class="icon icon--xs flip-x" - viewBox="0 0 16 16" - ><title>Go to previous page</title> - <polyline - points="6 2 12 8 6 14" - fill="none" - stroke="currentColor" - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - /> - </svg> - </button> - </li> - - <li> - <span class="pagination__jumper flex items-center"> - <input - aria-label="Page number" - class="form-control" - id="curr-page" - type="text" - on:change={handle_change} - value={page} - /> - <em>of {pageCount}</em> - </span> - </li> - - <li> - <button - on:click={increment} - class="reset pagination__item {canIncrement ? '' : 'c-disabled'}" - > - <svg class="icon icon--xs" - viewBox="0 0 16 16" - ><title>Go to next page</title> - <polyline - points="6 2 12 8 6 14" - fill="none" - stroke="currentColor" - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - /> - </svg> - </button> - </li> - </ul> -</nav> 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 deleted file mode 100644 index cb974ed..0000000 --- a/apps/projects-web/src/app/pages/views/entry-form/index.svelte +++ /dev/null @@ -1,196 +0,0 @@ -<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> 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 deleted file mode 100644 index f98c045..0000000 --- a/apps/projects-web/src/app/pages/views/entry-form/sections/category.svelte +++ /dev/null @@ -1,75 +0,0 @@ -<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 deleted file mode 100644 index c91e014..0000000 --- a/apps/projects-web/src/app/pages/views/entry-form/sections/date-time.svelte +++ /dev/null @@ -1,165 +0,0 @@ -<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 deleted file mode 100644 index 06c703d..0000000 --- a/apps/projects-web/src/app/pages/views/entry-form/sections/labels.svelte +++ /dev/null @@ -1,65 +0,0 @@ -<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} -/> diff --git a/apps/projects-web/src/app/pages/views/profile-modal.svelte b/apps/projects-web/src/app/pages/views/profile-modal.svelte deleted file mode 100644 index 839b59d..0000000 --- a/apps/projects-web/src/app/pages/views/profile-modal.svelte +++ /dev/null @@ -1,156 +0,0 @@ -<script> - import {update_profile} from "$shared/lib/api/user"; - import Modal from "$shared/components/modal.svelte"; - import Alert from "$shared/components/alert.svelte"; - import Button from "$shared/components/button.svelte"; - import {is_email} from "$shared/lib/helpers"; - import {api_base} from "$shared/lib/configuration"; - import {delete_user} from "$app/lib/services/user-service"; - import {get_session_data} from "$shared/lib/session"; - - const archiveLink = api_base("_/api/account/archive"); - - let modal; - let understands = false; - - let formIsLoading = false; - let formError; - - let username = get_session_data()?.profile.username; - let usernameFieldMessage; - let usernameFieldMessageClass = "color-error"; - - let password; - let passwordFieldMessage; - let passwordFieldMessageClass = "color-error"; - - async function submit_form(e) { - e.preventDefault(); - if (!username && !password) { - console.error("Not submitting becuase both values is empty"); - return; - } - - usernameFieldMessage = ""; - passwordFieldMessage = ""; - - if (username && !is_email(username)) { - usernameFieldMessage = "Username has to be a valid email"; - return; - } - - if (password && password?.length < 6) { - passwordFieldMessage = "The new password must contain at least 6 characters"; - return; - } - - formIsLoading = true; - - const response = await update_profile({ - username, - password, - }); - - formIsLoading = false; - - if (response.ok) { - if (password) { - passwordFieldMessage = "Successfully updated"; - passwordFieldMessageClass = "color-success"; - password = ""; - } - if (username) { - usernameFieldMessage = "Successfully updated"; - usernameFieldMessageClass = "color-success"; - password = ""; - } - } else { - formError = response.data.title ?? "An unknown error occured"; - } - } - - async function handle_delete_account_button_click() { - if (understands && confirm("Are you absolutely sure that you want to delete your account?")) { - await delete_user(); - } - } - - export const functions = { - open() { - modal.open(); - }, - close() { - // modal.close(); - }, - }; -</script> - -<Modal title="Profile" - bind:functions={modal}> - <section class="margin-bottom-md"> - <p class="text-md margin-bottom-sm">Update your information</p> - <form on:submit={submit_form} - autocomplete="new-password"> - {#if formError} - <small class="color-danger">{formError}</small> - {/if} - <div class="margin-bottom-sm"> - <label for="email" - class="form-label margin-bottom-xxs">New username</label> - <input type="email" - class="form-control width-100%" - id="email" - placeholder={username} - bind:value={username}/> - {#if usernameFieldMessage} - <small class={usernameFieldMessageClass}>{usernameFieldMessage}</small> - {/if} - </div> - <div class="margin-bottom-sm"> - <label for="password" - class="form-label margin-bottom-xxs">New password</label> - <input type="password" - class="form-control width-100%" - id="password" - bind:value={password}/> - {#if passwordFieldMessage} - <small class={passwordFieldMessageClass}>{passwordFieldMessage}</small> - {/if} - </div> - <div class="flex justify-end"> - <Button text="Save" - on:click={submit_form} - variant="primary" - loading={formIsLoading}/> - </div> - </form> - </section> - <section class="margin-bottom-md"> - <p class="text-md margin-bottom-sm">Download your data</p> - <a class="btn btn--subtle" - href={archiveLink} - download>Click here to download your data</a> - </section> - <section> - <p class="text-md margin-bottom-sm">Delete account</p> - <div class="margin-bottom-sm"> - <Alert - message="Deleting your account and data means that all of your data (entries, categories, etc.) will be unrecoverable forever.<br>You should probably download your data before continuing." - type="info" - /> - </div> - <div class="form-check margin-bottom-sm"> - <input type="checkbox" - class="checkbox" - id="the-consequences" - bind:checked={understands}/> - <label for="the-consequences">I understand the consequences of deleting my account and data.</label> - </div> - <div class="flex justify-end"> - <Button text="Delete everything" - variant="accent" - disabled={!understands} - on:click={handle_delete_account_button_click}/> - </div> - </section> -</Modal> diff --git a/apps/projects-web/src/app/pages/views/settings-categories-tile.svelte b/apps/projects-web/src/app/pages/views/settings-categories-tile.svelte deleted file mode 100644 index 890609a..0000000 --- a/apps/projects-web/src/app/pages/views/settings-categories-tile.svelte +++ /dev/null @@ -1,127 +0,0 @@ -<script> - import {IconNames} from "$shared/lib/configuration"; - import {onMount} from "svelte"; - import { - delete_time_category, - get_time_categories, - } from "$shared/lib/api/time-entry"; - import Button from "$shared/components/button.svelte"; - import Tile from "$shared/components/tile.svelte"; - import {Table, THead, TBody, TCell, TRow} from "$shared/components/table"; - - let is_loading = true; - let categories = []; - - $: active_categories = categories.filter(c => !c.archived); - $: archived_categories = categories.filter(c => c.archived); - - async function load_categories() { - is_loading = true; - const response = await get_time_categories(); - if (response.status === 200) { - categories = response.data; - } else if (response.status === 204) { - categories = []; - console.log("Empty response when getting time categories"); - } else { - categories = []; - console.error("Error when getting time categories"); - } - is_loading = false; - } - - async function handle_edit_category_click(event) { - } - - async function handle_delete_category_click(event) { - const row = event.target.closest("tr"); - if ( - row && - row.dataset.id && - confirm( - "Are you sure you want to delete this category?\nThis will delete all relating entries!" - ) - ) { - const response = await delete_time_category(row.dataset.id); - if (response.ok) { - // svelte errors if we remove the row. - row.classList.add("d-none"); - } - } - } - - onMount(() => { - load_categories(); - }); -</script> - -<Tile class="col-6@md col-12 {is_loading ? 'c-disabled loading' : ''}"> - <h2 class="margin-bottom-xxs">Categories</h2> - {#if active_categories.length > 0 && archived_categories.length > 0} - <nav class="s-tabs text-sm"> - <ul class="s-tabs__list"> - <li><a class="s-tabs__link s-tabs__link--current" - href="#0">Active ({active_categories.length})</a></li> - <li><a class="s-tabs__link" - href="#0">Archived ({archived_categories.length})</a></li> - </ul> - </nav> - {/if} - <div class="max-width-100% overflow-auto"> - <Table class="text-sm width-100%"> - <THead class="text-left"> - <TCell type="th" - thScope="col"> - Name - </TCell> - <TCell type="th" - thScope="col"> - Color - </TCell> - <TCell type="th" - thScope="col" - style="width:50px"></TCell> - </THead> - <TBody class="text-left"> - {#if categories.length > 0} - {#each categories as category} - <TRow class="text-nowrap" - data-id={category.id}> - <TCell> - {category.name} - </TCell> - <TCell> - <span style="border-left: 3px solid {category.color}; background-color:{category.color}25;"> - {category.color} - </span> - </TCell> - <TCell> - <Button icon="{IconNames.pencilSquare}" - variant="reset" - icon_width="1.2rem" - class="hide" - icon_height="1.2rem" - on:click={handle_edit_category_click} - title="Edit entry"/> - <Button icon="{IconNames.trash}" - variant="reset" - icon_width="1.2rem" - icon_height="1.2rem" - on:click={handle_delete_category_click} - title="Delete entry"/> - - </TCell> - </TRow> - {/each} - {:else} - <TRow> - <TCell type="th" - thScope="3"> - No categories - </TCell> - </TRow> - {/if} - </TBody> - </Table> - </div> -</Tile> diff --git a/apps/projects-web/src/app/pages/views/settings-labels-tile.svelte b/apps/projects-web/src/app/pages/views/settings-labels-tile.svelte deleted file mode 100644 index f59e233..0000000 --- a/apps/projects-web/src/app/pages/views/settings-labels-tile.svelte +++ /dev/null @@ -1,112 +0,0 @@ -<script> - import {IconNames} from "$shared/lib/configuration"; - import {onMount} from "svelte"; - import labels, {reload_labels, delete_label_async} from "$app/lib/stores/labels"; - import Button from "$shared/components/button.svelte"; - import Tile from "$shared/components/tile.svelte"; - import {Table, THead, TBody, TCell, TRow} from "$shared/components/table"; - - let is_loading = true; - - $: active_labels = $labels.filter(c => !c.archived); - $: archived_labels = $labels.filter(c => c.archived); - - async function load_labels() { - is_loading = true; - await reload_labels(); - is_loading = false; - } - - async function handle_edit_label_click(event) { - } - - async function handle_delete_label_click(event) { - const row = event.target.closest("tr"); - if ( - row && - row.dataset.id && - confirm( - "Are you sure you want to delete this label?\nIt will be removed from all related entries!" - ) - ) { - await delete_label_async({id: row.dataset.id}); - row.classList.add("d-none"); - } - } - - onMount(() => { - load_labels(); - }); -</script> - -<Tile class="col-6@md col-12 {is_loading ? 'c-disabled loading' : ''}"> - <h2 class="margin-bottom-xxs">Labels</h2> - {#if active_labels.length > 0 && archived_labels.length > 0} - <nav class="s-tabs text-sm"> - <ul class="s-tabs__list"> - <li><a class="s-tabs__link s-tabs__link--current" - href="#0">Active ({active_labels.length})</a></li> - <li><a class="s-tabs__link" - href="#0">Archived ({archived_labels.length})</a></li> - </ul> - </nav> - {/if} - <div class="max-width-100% overflow-auto"> - <Table class="text-sm width-100%"> - <THead class="text-left"> - <TCell type="th" - thScope="row"> - Name - </TCell> - <TCell type="th" - thScope="row"> - Color - </TCell> - <TCell type="th" - thScope="row" - style="width: 50px;"> - </TCell> - </THead> - <TBody class="text-left"> - {#if $labels.length > 0} - {#each $labels as label} - <TRow class="text-nowrap" - dataId={label.id}> - <TCell> - {label.name} - </TCell> - <TCell> - <span style="border-left: 3px solid {label.color}; background-color:{label.color}25;"> - {label.color} - </span> - </TCell> - <TCell> - <Button icon="{IconNames.pencilSquare}" - variant="reset" - icon_width="1.2rem" - class="hide" - icon_height="1.2rem" - on:click={handle_edit_label_click} - title="Edit entry"/> - <Button icon="{IconNames.trash}" - variant="reset" - icon_width="1.2rem" - icon_height="1.2rem" - on:click={handle_delete_label_click} - title="Delete entry"/> - </TCell> - </TRow> - {/each} - {:else} - <TRow> - <TCell type="th" - thScope="row" - colspan="3"> - No labels - </TCell> - </TRow> - {/if} - </TBody> - </Table> - </div> -</Tile> |
