diff options
Diffstat (limited to 'apps/projects-web/src/app/pages/data.svelte')
| -rw-r--r-- | apps/projects-web/src/app/pages/data.svelte | 392 |
1 files changed, 0 insertions, 392 deletions
diff --git a/apps/projects-web/src/app/pages/data.svelte b/apps/projects-web/src/app/pages/data.svelte deleted file mode 100644 index 070b98b..0000000 --- a/apps/projects-web/src/app/pages/data.svelte +++ /dev/null @@ -1,392 +0,0 @@ -<script> - import {IconNames} from "$shared/lib/configuration"; - import {onMount} from "svelte"; - import {Temporal} from "@js-temporal/polyfill"; - import Layout from "./_layout.svelte"; - import Modal from "$shared/components/modal.svelte"; - import Tile from "$shared/components/tile.svelte"; - import Icon from "$shared/components/icon.svelte"; - import EntryForm from "$app/pages/views/entry-form/index.svelte"; - import {Table, THead, TBody, TCell, TRow, TablePaginator} from "$shared/components/table"; - import {TimeEntryQueryDuration} from "$shared/lib/models/TimeEntryQuery"; - import {delete_time_entry, get_time_entries, get_time_entry} from "$shared/lib/api/time-entry"; - import {seconds_to_hour_minute_string, is_guid, move_focus, unwrap_date_time_from_entry} from "$shared/lib/helpers"; - import Button from "$shared/components/button.svelte"; - - let pageCount = 1; - let page = 1; - - const defaultQuery = { - duration: TimeEntryQueryDuration.THIS_YEAR, - categories: [], - labels: [], - page: page, - pageSize: 50, - }; - - let isLoading; - let categories = []; - let labels = []; - let entries = []; - let durationSummary = false; - let EditEntryModal; - let EditEntryForm; - let currentTimespanFilter = TimeEntryQueryDuration.THIS_YEAR; - let currentSpecificDateFilter = Temporal.Now.plainDateTimeISO().subtract({days: 1}).toString().substring(0, 10); - let currentDateRangeFilter = {}; - let currentCategoryFilter = "all"; - let currentLabelFilter = "all"; - let showDateFilterOptions = false; - let secondsLogged = 0; - - function set_duration_summary_string() { - if (entries.length > 0) { - durationSummary = `Showing ${entries.length} ${entries.length === 1 ? "entry" : "entries"}, totalling in ${seconds_to_hour_minute_string(secondsLogged)}`; - } else { - durationSummary = ""; - } - } - - async function load_entries(query = defaultQuery) { - isLoading = true; - const response = await get_time_entries(query); - if (response.status === 200) { - const responseEntries = []; - secondsLogged = 0; - for (const entry of response.data.results) { - const date_time = unwrap_date_time_from_entry(entry); - const seconds = (date_time.duration.hours * 60 * 60) + (date_time.duration.minutes * 60); - responseEntries.push({ - id: entry.id, - date: date_time.start_date, - start: date_time.start_time, - stop: date_time.stop_time, - durationString: date_time.duration.hours + "h" + date_time.duration.minutes + "m", - seconds: seconds, - category: entry.category, - labels: entry.labels, - description: entry.description, - }); - secondsLogged += seconds; - } - entries = responseEntries; - page = response.data.page; - pageCount = response.data.totalPageCount; - } else { - entries = []; - page = 0; - pageCount = 0; - } - isLoading = false; - set_duration_summary_string(); - } - - function load_entries_with_filter(page = 1) { - let query = defaultQuery; - query.duration = currentTimespanFilter; - query.labels = []; - query.categories = []; - query.page = page; - - if (currentTimespanFilter === TimeEntryQueryDuration.SPECIFIC_DATE) { - query.specificDate = currentSpecificDateFilter; - } else { - delete query.specificDate; - } - - if (currentTimespanFilter === TimeEntryQueryDuration.DATE_RANGE) { - query.dateRange = currentDateRangeFilter; - } else { - delete query.dateRange; - } - - if ((currentCategoryFilter !== "all" && currentCategoryFilter?.length > 0) ?? false) { - for (const chosenCategoryId of currentCategoryFilter) { - if (chosenCategoryId === "all") { - continue; - } - query.categories.push({ - id: chosenCategoryId, - }); - } - } - - if ((currentLabelFilter !== "all" && currentLabelFilter?.length > 0) ?? false) { - for (const chosenLabelId of currentLabelFilter) { - if (chosenLabelId === "all") { - continue; - } - query.labels.push({ - id: chosenLabelId, - }); - } - } - - load_entries(query); - } - - async function handle_delete_entry_button_click(e, entryId) { - if (confirm("Are you sure you want to delete this entry?")) { - const response = await delete_time_entry(entryId); - if (response.ok) { - const indexOfEntry = entries.findIndex((c) => c.id === entryId); - if (indexOfEntry !== -1) { - secondsLogged -= entries[indexOfEntry].seconds; - entries.splice(indexOfEntry, 1); - entries = entries; - set_duration_summary_string(); - } - } - } - } - - function handle_edit_entry_form_updated() { - load_entries_with_filter(page); - EditEntryModal.close(); - } - - async function handle_edit_entry_button_click(event, entryId) { - const response = await get_time_entry(entryId); - if (response.status === 200) { - if (is_guid(response.data.id)) { - EditEntryForm.set_values(response.data); - EditEntryModal.open(); - move_focus(document.querySelector("input[id='date']")); - } - } - } - - function close_date_filter_box(event) { - if (!event.target.closest(".date_filter_box_el")) { - showDateFilterOptions = false; - window.removeEventListener("click", close_date_filter_box); - } - } - - function toggle_date_filter_box(event) { - const box = document.getElementById("date_filter_box"); - const rect = event.target.getBoundingClientRect(); - box.style.top = rect.y + "px"; - box.style.left = rect.x - 50 + "px"; - showDateFilterOptions = true; - window.addEventListener("click", close_date_filter_box); - } - - onMount(() => { - isLoading = true; - Promise.all([load_entries()]).then(() => { - isLoading = false; - }); - }); -</script> - -<Modal title="Edit entry" - bind:functions={EditEntryModal} - on:closed={() => EditEntryForm.reset()}> - <EntryForm bind:functions={EditEntryForm} - on:updated={handle_edit_entry_form_updated}/> -</Modal> - -<div id="date_filter_box" - style="margin-top:25px" - class="padding-xs z-index-overlay bg shadow-sm position-absolute date_filter_box_el border {showDateFilterOptions ? '' : 'hide'}"> - <div class="flex items-baseline margin-bottom-xxxxs"> - <label class="text-sm color-contrast-medium margin-right-xs" - for="durationSelect">Timespan:</label> - <div class="select inline-block js-select"> - <select name="durationSelect" - bind:value={currentTimespanFilter} - id="durationSelect"> - <option value={TimeEntryQueryDuration.TODAY} - selected> Today - </option> - <option value={TimeEntryQueryDuration.THIS_WEEK}>This week</option> - <option value={TimeEntryQueryDuration.THIS_MONTH}>This month</option> - <option value={TimeEntryQueryDuration.THIS_YEAR}>This year</option> - <option value={TimeEntryQueryDuration.SPECIFIC_DATE}>Spesific date</option> - <option value={TimeEntryQueryDuration.DATE_RANGE}>Date range</option> - </select> - - <svg class="icon icon--xxxs margin-left-xxs" - viewBox="0 0 8 8"> - <path d="M7.934,1.251A.5.5,0,0,0,7.5,1H.5a.5.5,0,0,0-.432.752l3.5,6a.5.5,0,0,0,.864,0l3.5-6A.5.5,0,0,0,7.934,1.251Z"/> - </svg> - </div> - </div> - - {#if currentTimespanFilter === TimeEntryQueryDuration.SPECIFIC_DATE} - <div class="flex items-baseline margin-bottom-xxxxs justify-between"> - <span class="text-sm color-contrast-medium margin-right-xs">Date:</span> - <span class="text-sm"> - <input type="date" - class="border-none padding-0 color-inherit bg-transparent" - bind:value={currentSpecificDateFilter}/> - </span> - </div> - {/if} - - {#if currentTimespanFilter === TimeEntryQueryDuration.DATE_RANGE} - <div class="flex items-baseline margin-bottom-xxxxs justify-between"> - <span class="text-sm color-contrast-medium margin-right-xs">From:</span> - <span class="text-sm"> - <input type="date" - class="border-none padding-0 color-inherit bg-transparent" - on:change={(e) => (currentDateRangeFilter.from = e.target.value)}/> - </span> - </div> - - <div class="flex items-baseline margin-bottom-xxxxs justify-between"> - <span class="text-sm color-contrast-medium margin-right-xs">To:</span> - <span class="text-sm"> - <input type="date" - class="border-none padding-0 color-inherit bg-transparent" - on:change={(e) => (currentDateRangeFilter.to = e.target.value)}/> - </span> - </div> - {/if} - - <div class="flex items-baseline justify-end"> - <Button variant="subtle" - on:click={() => load_entries_with_filter(page)} - class="text-sm" - text="Save"/> - </div> -</div> - -<Layout> - <Tile class="{isLoading ? 'c-disabled loading' : ''}"> - <nav class="s-tabs text-sm"> - <ul class="s-tabs__list"> - <li><span class="s-tabs__link s-tabs__link--current">All (21)</span></li> - <li><span class="s-tabs__link">Published (19)</span></li> - <li><span class="s-tabs__link">Draft (2)</span></li> - </ul> - </nav> - <div class="max-width-100% overflow-auto" - style="max-height: 82.5vh"> - <Table class="text-sm width-100% int-table--sticky-header"> - <THead> - <TCell type="th" - style="width: 30px;"> - <div class="custom-checkbox int-table__checkbox"> - <input class="custom-checkbox__input" - type="checkbox" - aria-label="Select all rows"/> - <div class="custom-checkbox__control" - aria-hidden="true"></div> - </div> - </TCell> - - <TCell type="th" - style="width: 100px"> - <div class="flex items-center justify-between"> - <span>Date</span> - <div class="date_filter_box_el cursor-pointer" - on:click={toggle_date_filter_box}> - <Icon name="{IconNames.funnel}"/> - </div> - </div> - </TCell> - - <TCell type="th" - style="width: 100px"> - <div class="flex items-center"> - <span>Duration</span> - </div> - </TCell> - - <TCell type="th" - style="width: 100px;"> - <div class="flex items-center"> - <span>Category</span> - </div> - </TCell> - - <TCell type="th" - style="width: 300px;"> - <div class="flex items-center"> - <span>Description</span> - </div> - </TCell> - <TCell type="th" - style="width: 50px"></TCell> - </THead> - <TBody> - {#if entries.length > 0} - {#each entries as entry} - <TRow class="text-nowrap" - data-id={entry.id}> - <TCell type="th" - thScope="row"> - <div class="custom-checkbox int-table__checkbox"> - <input class="custom-checkbox__input" - type="checkbox" - aria-label="Select this row"/> - <div class="custom-checkbox__control" - aria-hidden="true"></div> - </div> - </TCell> - <TCell> - <pre>{entry.date.toLocaleString()}</pre> - </TCell> - <TCell> - <pre class="flex justify-between"> - <div class="flex justify-between"> - <span>{entry.start.toLocaleString(undefined, {timeStyle: "short"})}</span> - <span> - </span> - <span>{entry.stop.toLocaleString(undefined, {timeStyle: "short"})}</span> - </div> - </pre> - </TCell> - <TCell> - <span data-id={entry.category.id}>{entry.category.name}</span> - </TCell> - <TCell class="text-truncate max-width-xxxxs" - title="{entry.description}"> - {entry.description ?? ""} - </TCell> - <TCell class="flex flex-row justify-end items-center"> - <Button icon="{IconNames.pencilSquare}" - variant="reset" - icon_width="1.2rem" - icon_height="1.2rem" - on:click={(e) => handle_edit_entry_button_click(e, entry.id)} - title="Edit entry"/> - <Button icon="{IconNames.trash}" - variant="reset" - icon_width="1.2rem" - icon_height="1.2rem" - on:click={(e) => handle_delete_entry_button_click(e, entry.id)} - title="Delete entry"/> - </TCell> - </TRow> - {/each} - {:else} - <TRow class="text-nowrap"> - <TCell type="th" - thScope="row" - colspan="7"> - {isLoading ? "Loading..." : "No entries"} - </TCell> - </TRow> - {/if} - </TBody> - </Table> - </div> - <div class="flex items-center justify-between"> - <p class="text-sm"> - {#if durationSummary} - <small class={isLoading ? "c-disabled loading" : ""}>{durationSummary}</small> - {:else} - <small class={isLoading ? "c-disabled loading" : ""}>No entries</small> - {/if} - </p> - - <nav class="grid padding-y-sm {isLoading ? 'c-disabled loading' : ''}"> - <TablePaginator {page} - on:value_change={(e) => load_entries_with_filter(e.detail.newValue)} - {pageCount}/> - </nav> - </div> - </Tile> -</Layout> |
