aboutsummaryrefslogtreecommitdiffstats
path: root/code/app/src/routes/(main)/(app)/projects/+page.svelte
diff options
context:
space:
mode:
Diffstat (limited to 'code/app/src/routes/(main)/(app)/projects/+page.svelte')
-rw-r--r--code/app/src/routes/(main)/(app)/projects/+page.svelte228
1 files changed, 117 insertions, 111 deletions
diff --git a/code/app/src/routes/(main)/(app)/projects/+page.svelte b/code/app/src/routes/(main)/(app)/projects/+page.svelte
index e39a886..1508118 100644
--- a/code/app/src/routes/(main)/(app)/projects/+page.svelte
+++ b/code/app/src/routes/(main)/(app)/projects/+page.svelte
@@ -1,137 +1,143 @@
<script lang="ts">
- import { Button, ProjectStatusBadge, Input } from "$lib/components";
- import type { Project } from "$lib/models/projects/Project";
- import { onMount } from "svelte";
- import { faker } from "@faker-js/faker";
- import { Temporal } from "temporal-polyfill";
- import { createTable, Subscribe, Render } from "svelte-headless-table";
- import { addSortBy, addTableFilter } from "svelte-headless-table/plugins";
- import { ProjectStatus } from "$lib/models/projects/ProjectStatus";
- import { writable, type Writable } from "svelte/store";
- import { ChevronDownIcon, ChevronUpIcon, ChevronUpDownIcon, MagnifyingGlassIcon, FunnelIcon } from "$lib/components/icons";
- import LL from "$lib/i18n/i18n-svelte";
- import { goto } from "$app/navigation";
+ import {Button, ProjectStatusBadge, Input} from "$components";
+ import type {Project} from "$models/projects/Project";
+ import {onMount} from "svelte";
+ import {faker} from "@faker-js/faker";
+ import {Temporal} from "temporal-polyfill";
+ import {createTable, Subscribe, Render} from "svelte-headless-table";
+ import {addSortBy, addTableFilter} from "svelte-headless-table/plugins";
+ import {ProjectStatus} from "$models/projects/ProjectStatus";
+ import {writable, type Writable} from "svelte/store";
+ import {
+ ChevronDownIcon,
+ ChevronUpIcon,
+ ChevronUpDownIcon,
+ MagnifyingGlassIcon,
+ FunnelIcon,
+ } from "$components/icons";
+ import LL from "$i18n/i18n-svelte";
+ import {goto} from "$app/navigation";
- let projects: Writable<Array<Project>> = writable([]);
+ let projects: Writable<Array<Project>> = writable([]);
- onMount(() => {
- let i = 0;
- const tempProjects = [];
- while (i < 101) {
- tempProjects.push({
- id: crypto.randomUUID(),
- name: faker.lorem.word(),
- start: Temporal.Now.plainDateISO().toLocaleString(),
- description: faker.lorem.words(3),
- members: [],
- status: ProjectStatus.IDLE,
- });
- i++;
- }
- projects.set(tempProjects);
- });
+ onMount(() => {
+ let i = 0;
+ const tempProjects = [];
+ while (i < 101) {
+ tempProjects.push({
+ id: crypto.randomUUID(),
+ name: faker.lorem.word(),
+ start: Temporal.Now.plainDateISO().toLocaleString(),
+ description: faker.lorem.words(3),
+ members: [],
+ status: ProjectStatus.IDLE,
+ });
+ i++;
+ }
+ projects.set(tempProjects);
+ });
- function on_open_project(event) {
- if (event.code && (event.code !== "Enter" || event.code !== "Space")) return;
- const name = event.target.innerText;
- const projectId = $projects.find((p) => p.name === name).id;
- goto("/projects/" + projectId);
- }
+ function on_open_project(event) {
+ if (event.code && (event.code !== "Enter" || event.code !== "Space")) return;
+ const name = event.target.innerText;
+ const projectId = $projects.find((p) => p.name === name).id;
+ goto("/projects/" + projectId);
+ }
- const table = createTable(projects, {
- sort: addSortBy(),
- filter: addTableFilter(),
- });
+ const table = createTable(projects, {
+ sort: addSortBy(),
+ filter: addTableFilter(),
+ });
- const columns = table.createColumns([
- table.column({ header: $LL.name(), accessor: "name" }),
- table.column({ header: "Status", accessor: "status" }),
- table.column({ header: "Start", accessor: "start" }),
- table.column({ header: "Description", accessor: "description", plugins: { sort: { disable: true } } }),
- ]);
+ const columns = table.createColumns([
+ table.column({header: $LL.name(), accessor: "name"}),
+ table.column({header: "Status", accessor: "status"}),
+ table.column({header: "Start", accessor: "start"}),
+ table.column({header: "Description", accessor: "description", plugins: {sort: {disable: true}}}),
+ ]);
- const { headerRows, rows, tableAttrs, tableBodyAttrs, pluginStates } = table.createViewModel(columns);
- const { filterValue } = pluginStates.filter;
+ const {headerRows, rows, tableAttrs, tableBodyAttrs, pluginStates} = table.createViewModel(columns);
+ const {filterValue} = pluginStates.filter;
</script>
<div class="sm:flex sm:items-center">
- <div class="sm:flex-auto">
- <h1 class="text-xl font-semibold text-gray-900">Projects</h1>
- <p class="mt-2 text-sm text-gray-700">A list of all the projects in your organsation.</p>
- </div>
- <div class="mt-4 sm:mt-0 sm:ml-16 inline-flex gap-1 sm:flex-none">
- <Input icon={MagnifyingGlassIcon} placeholder="Search" bind:value={$filterValue} />
- <Button text="Create project" href="/projects/create" />
- </div>
+ <div class="sm:flex-auto">
+ <h1 class="text-xl font-semibold text-gray-900">Projects</h1>
+ <p class="mt-2 text-sm text-gray-700">A list of all the projects in your organsation.</p>
+ </div>
+ <div class="mt-4 sm:mt-0 sm:ml-16 inline-flex gap-1 sm:flex-none">
+ <Input icon={MagnifyingGlassIcon} placeholder="Search" bind:value={$filterValue}/>
+ <Button text="Create project" href="/projects/create"/>
+ </div>
</div>
<div class="-mx-2 mt-6 rounded-md shadow overflow-auto max-h-[80vh] sm:-mx-6 md:mx-0">
- <table {...$tableAttrs} class="min-w-full divide-y divide-gray-300">
- <thead class="bg-gray-50">
- {#each $headerRows as headerRow (headerRow.id)}
- <Subscribe rowAttrs={headerRow.attrs()} let:rowAttrs>
- <tr {...rowAttrs} class="shadow-sm">
- {#each headerRow.cells as cell (cell.id)}
- <Subscribe attrs={cell.attrs()} let:attrs props={cell.props()} let:props>
- <th
- {...attrs}
- scope="col"
- class="sticky top-0 bg-gray-50 bg-opacity-100 whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900"
- >
- <div class="group inline-flex">
- <Render of={cell.render()} />
- <span
- on:click={props.sort.toggle}
- on:keypress={props.sort.toggle}
- class="{props.sort.disabled
+ <table {...$tableAttrs} class="min-w-full divide-y divide-gray-300">
+ <thead class="bg-gray-50">
+ {#each $headerRows as headerRow (headerRow.id)}
+ <Subscribe rowAttrs={headerRow.attrs()} let:rowAttrs>
+ <tr {...rowAttrs} class="shadow-sm">
+ {#each headerRow.cells as cell (cell.id)}
+ <Subscribe attrs={cell.attrs()} let:attrs props={cell.props()} let:props>
+ <th
+ {...attrs}
+ scope="col"
+ class="sticky top-0 bg-gray-50 bg-opacity-100 whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900"
+ >
+ <div class="group inline-flex">
+ <Render of={cell.render()}/>
+ <span
+ on:click={props.sort.toggle}
+ on:keypress={props.sort.toggle}
+ class="{props.sort.disabled
? 'bg-gray-200 text-gray-900 group-hover:bg-gray-300'
: 'invisible text-gray-400 group-hover:visible group-focus:visible'}
{props.sort.disabled ? '' : 'cursor-pointer'}
ml-2 flex-none rounded"
- >
+ >
{#if props.sort.order === "asc"}
- <ChevronUpIcon />
+ <ChevronUpIcon/>
{:else if props.sort.order === "desc"}
- <ChevronDownIcon />
+ <ChevronDownIcon/>
{:else if !props.sort.disabled}
- <ChevronUpDownIcon />
+ <ChevronUpDownIcon/>
{/if}
</span>
- {#if cell.id === "status"}
+ {#if cell.id === "status"}
<span class="invisible text-gray-400 cursor-pointer group-hover:visible group-focus:visible ml-2 flex-none rounded">
- <FunnelIcon />
+ <FunnelIcon/>
</span>
- {/if}
- </div>
- </th>
- </Subscribe>
- {/each}
- </tr>
- </Subscribe>
- {/each}
- </thead>
- <tbody {...$tableBodyAttrs} class="divide-y divide-gray-200 bg-white">
- {#each $rows as row (row.id)}
- <Subscribe rowAttrs={row.attrs()} let:rowAttrs>
- <tr {...rowAttrs}>
- {#each row.cells as cell (cell.id)}
- {@const materialisedCell = cell.render()}
- <Subscribe attrs={cell.attrs()} let:attrs>
- <td {...attrs} class="whitespace-nowrap px-2 py-2 text-sm">
- {#if cell.id === "name"}
+ {/if}
+ </div>
+ </th>
+ </Subscribe>
+ {/each}
+ </tr>
+ </Subscribe>
+ {/each}
+ </thead>
+ <tbody {...$tableBodyAttrs} class="divide-y divide-gray-200 bg-white">
+ {#each $rows as row (row.id)}
+ <Subscribe rowAttrs={row.attrs()} let:rowAttrs>
+ <tr {...rowAttrs}>
+ {#each row.cells as cell (cell.id)}
+ {@const materialisedCell = cell.render()}
+ <Subscribe attrs={cell.attrs()} let:attrs>
+ <td {...attrs} class="whitespace-nowrap px-2 py-2 text-sm">
+ {#if cell.id === "name"}
<span class="link" title="Open project" on:click={on_open_project} on:keypress={on_open_project}>
- <Render of={materialisedCell} />
+ <Render of={materialisedCell}/>
</span>
- {:else if cell.id === "status"}
- <ProjectStatusBadge status={materialisedCell.toString()} />
- {:else}
- <Render of={materialisedCell} />
- {/if}
- </td>
- </Subscribe>
- {/each}
- </tr>
- </Subscribe>
- {/each}
- </tbody>
- </table>
+ {:else if cell.id === "status"}
+ <ProjectStatusBadge status={materialisedCell.toString()}/>
+ {:else}
+ <Render of={materialisedCell}/>
+ {/if}
+ </td>
+ </Subscribe>
+ {/each}
+ </tr>
+ </Subscribe>
+ {/each}
+ </tbody>
+ </table>
</div>