summaryrefslogtreecommitdiffstats
path: root/apps/projects-web/src/app/pages/views
diff options
context:
space:
mode:
Diffstat (limited to 'apps/projects-web/src/app/pages/views')
-rw-r--r--apps/projects-web/src/app/pages/views/category-form/index.svelte144
-rw-r--r--apps/projects-web/src/app/pages/views/data-table-paginator.svelte107
-rw-r--r--apps/projects-web/src/app/pages/views/entry-form/index.svelte196
-rw-r--r--apps/projects-web/src/app/pages/views/entry-form/sections/category.svelte75
-rw-r--r--apps/projects-web/src/app/pages/views/entry-form/sections/date-time.svelte165
-rw-r--r--apps/projects-web/src/app/pages/views/entry-form/sections/labels.svelte65
-rw-r--r--apps/projects-web/src/app/pages/views/profile-modal.svelte156
-rw-r--r--apps/projects-web/src/app/pages/views/settings-categories-tile.svelte127
-rw-r--r--apps/projects-web/src/app/pages/views/settings-labels-tile.svelte112
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>