aboutsummaryrefslogtreecommitdiffstats
path: root/code/app/src/routes/(main)/(app)
diff options
context:
space:
mode:
Diffstat (limited to 'code/app/src/routes/(main)/(app)')
-rw-r--r--code/app/src/routes/(main)/(app)/+layout.svelte534
-rw-r--r--code/app/src/routes/(main)/(app)/projects/+page.svelte228
-rw-r--r--code/app/src/routes/(main)/(app)/projects/create/+page.svelte20
-rw-r--r--code/app/src/routes/(main)/(app)/settings/+page.svelte106
4 files changed, 459 insertions, 429 deletions
diff --git a/code/app/src/routes/(main)/(app)/+layout.svelte b/code/app/src/routes/(main)/(app)/+layout.svelte
index a280fa7..6cb70ef 100644
--- a/code/app/src/routes/(main)/(app)/+layout.svelte
+++ b/code/app/src/routes/(main)/(app)/+layout.svelte
@@ -1,151 +1,162 @@
<script lang="ts">
- import {
- ChevronUpDownIcon,
- MagnifyingGlassIcon,
- Bars3CenterLeftIcon,
- XMarkIcon,
- HomeIcon,
- MegaphoneIcon,
- FolderOpenIcon,
- QueueListIcon,
- CalendarIcon,
- } from "$lib/components/icons";
- import { Dialog, Menu, MenuButton, MenuItem, MenuItems, Transition, TransitionChild, TransitionRoot } from "@rgossiaux/svelte-headlessui";
- import { DialogPanel } from "@developermuch/dev-svelte-headlessui";
- import { Input } from "$lib/components";
- import { end_session } from "$lib/session";
- import { goto } from "$app/navigation";
- import { page } from "$app/stores";
+ import {
+ ChevronUpDownIcon,
+ MagnifyingGlassIcon,
+ Bars3CenterLeftIcon,
+ XMarkIcon,
+ HomeIcon,
+ MegaphoneIcon,
+ FolderOpenIcon,
+ QueueListIcon,
+ CalendarIcon,
+ } from "$components/icons";
+ import {AccountService} from "$services/account-service";
+ import {
+ Dialog,
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuItems,
+ Transition,
+ TransitionChild,
+ TransitionRoot,
+ } from "@rgossiaux/svelte-headlessui";
+ import {DialogPanel} from "@developermuch/dev-svelte-headlessui";
+ import {Input} from "$components";
+ import {goto} from "$app/navigation";
+ import {page} from "$app/stores";
+
+ const accountService = new AccountService();
- const session = {
- profile: {
- username: "Brukernavn",
- displayName: "epost@adresse.no",
- },
- };
+ const session = {
+ profile: {
+ username: "Brukernavn",
+ displayName: "epost@adresse.no",
+ },
+ };
- let sidebarOpen = false;
- let sidebarSearchValue: string | undefined;
+ let sidebarOpen = false;
+ let sidebarSearchValue: string | undefined;
- function sign_out() {
- end_session(() => goto("/sign-in"));
- }
+ function sign_out() {
+ accountService.end_session(() => goto("/sign-in"));
+ }
- const navigationItems = [
- {
- href: "/home",
- name: "Home",
- icon: HomeIcon,
- },
- {
- href: "/projects",
- name: "Projects",
- icon: CalendarIcon,
- },
- {
- href: "/tickets",
- name: "Tickets",
- icon: MegaphoneIcon,
- },
- {
- href: "/todo",
- name: "Todo",
- icon: QueueListIcon,
- },
- {
- href: "/wiki",
- name: "Wiki",
- icon: FolderOpenIcon,
- },
- ];
+ const navigationItems = [
+ {
+ href: "/home",
+ name: "Home",
+ icon: HomeIcon,
+ },
+ {
+ href: "/projects",
+ name: "Projects",
+ icon: CalendarIcon,
+ },
+ {
+ href: "/tickets",
+ name: "Tickets",
+ icon: MegaphoneIcon,
+ },
+ {
+ href: "/todo",
+ name: "Todo",
+ icon: QueueListIcon,
+ },
+ {
+ href: "/wiki",
+ name: "Wiki",
+ icon: FolderOpenIcon,
+ },
+ ];
</script>
<div class="min-h-full">
- <!-- Mobile sidebar -->
- <TransitionRoot show={sidebarOpen}>
- <Dialog as="div" class="relative z-40 lg:hidden" on:close={() => (sidebarOpen = false)}>
- <TransitionChild
- as="div"
- enter="transition-opacity ease-linear duration-300"
- enterFrom="opacity-0"
- enterTo="opacity-100"
- leave="transition-opacity ease-linear duration-300"
- leaveFrom="opacity-100"
- leaveTo="opacity-0"
- >
- <div class="fixed inset-0 bg-gray-600 bg-opacity-75" />
- </TransitionChild>
-
- <div class="fixed inset-0 z-40 flex">
- <TransitionChild
- as="div"
- enter="transition ease-in-out duration-300 transform"
- enterFrom="-translate-x-full"
- enterTo="translate-x-0"
- leave="transition ease-in-out duration-300 transform"
- leaveFrom="translate-x-0"
- leaveTo="-translate-x-full"
- >
- <DialogPanel class="relative flex w-full max-w-xs flex-1 flex-col bg-white pt-5 pb-4">
+ <!-- Mobile sidebar -->
+ <TransitionRoot show={sidebarOpen}>
+ <Dialog as="div" class="relative z-40 lg:hidden" on:close={() => (sidebarOpen = false)}>
<TransitionChild
- as="div"
- enter="ease-in-out duration-300"
- enterFrom="opacity-0"
- enterTo="opacity-100"
- leave="ease-in-out duration-300"
- leaveFrom="opacity-100"
- leaveTo="opacity-0"
+ as="div"
+ enter="transition-opacity ease-linear duration-300"
+ enterFrom="opacity-0"
+ enterTo="opacity-100"
+ leave="transition-opacity ease-linear duration-300"
+ leaveFrom="opacity-100"
+ leaveTo="opacity-0"
>
- <div class="absolute top-0 right-0 -mr-12 pt-2">
- <button
- type="button"
- class="ml-1 flex h-10 w-10 items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
- on:click={() => (sidebarOpen = false)}
- >
- <span class="sr-only">Close sidebar</span>
- <XMarkIcon class="text-white" aria-hidden="true" />
- </button>
- </div>
+ <div class="fixed inset-0 bg-gray-600 bg-opacity-75"/>
</TransitionChild>
- <div class="mt-5 h-0 flex-1 overflow-y-auto">
- <nav class="px-2">
- <div class="space-y-1">
- {#each navigationItems as item}
- {@const current = $page.url.pathname.startsWith(item.href)}
- <a
- href={item.href}
- aria-current={current ? "page" : undefined}
- class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md
+
+ <div class="fixed inset-0 z-40 flex">
+ <TransitionChild
+ as="div"
+ enter="transition ease-in-out duration-300 transform"
+ enterFrom="-translate-x-full"
+ enterTo="translate-x-0"
+ leave="transition ease-in-out duration-300 transform"
+ leaveFrom="translate-x-0"
+ leaveTo="-translate-x-full"
+ >
+ <DialogPanel class="relative flex w-full max-w-xs flex-1 flex-col bg-white pt-5 pb-4">
+ <TransitionChild
+ as="div"
+ enter="ease-in-out duration-300"
+ enterFrom="opacity-0"
+ enterTo="opacity-100"
+ leave="ease-in-out duration-300"
+ leaveFrom="opacity-100"
+ leaveTo="opacity-0"
+ >
+ <div class="absolute top-0 right-0 -mr-12 pt-2">
+ <button
+ type="button"
+ class="ml-1 flex h-10 w-10 items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
+ on:click={() => (sidebarOpen = false)}
+ >
+ <span class="sr-only">Close sidebar</span>
+ <XMarkIcon class="text-white" aria-hidden="true"/>
+ </button>
+ </div>
+ </TransitionChild>
+ <div class="mt-5 h-0 flex-1 overflow-y-auto">
+ <nav class="px-2">
+ <div class="space-y-1">
+ {#each navigationItems as item}
+ {@const current = $page.url.pathname.startsWith(item.href)}
+ <a
+ href={item.href}
+ aria-current={current ? "page" : undefined}
+ class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md
{current ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'}"
- >
- <svelte:component
- this={item.icon}
- class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}"
- aria-hidden="true"
- />
- {item.name}
- </a>
- {/each}
+ >
+ <svelte:component
+ this={item.icon}
+ class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}"
+ aria-hidden="true"
+ />
+ {item.name}
+ </a>
+ {/each}
+ </div>
+ </nav>
+ </div>
+ </DialogPanel>
+ </TransitionChild>
+ <div class="w-14 flex-shrink-0" aria-hidden="true">
+ <!-- Dummy element to force sidebar to shrink to fit close icon -->
</div>
- </nav>
</div>
- </DialogPanel>
- </TransitionChild>
- <div class="w-14 flex-shrink-0" aria-hidden="true">
- <!-- Dummy element to force sidebar to shrink to fit close icon -->
- </div>
- </div>
- </Dialog>
- </TransitionRoot>
+ </Dialog>
+ </TransitionRoot>
- <!-- Static sidebar for desktop -->
- <div class="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col lg:border-r lg:border-gray-200 lg:bg-gray-100 lg:pb-4">
- <div class="flex h-0 flex-1 p-3 flex-col overflow-y-auto">
- <!-- User account dropdown -->
- <Menu class="relative inline-block text-left">
- <MenuButton
- class="group w-full rounded-md bg-gray-100 px-3.5 py-2 text-left text-sm font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 focus:ring-offset-gray-100"
- >
+ <!-- Static sidebar for desktop -->
+ <div class="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col lg:border-r lg:border-gray-200 lg:bg-gray-100 lg:pb-4">
+ <div class="flex h-0 flex-1 p-3 flex-col overflow-y-auto">
+ <!-- User account dropdown -->
+ <Menu class="relative inline-block text-left">
+ <MenuButton
+ class="group w-full rounded-md bg-gray-100 px-3.5 py-2 text-left text-sm font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 focus:ring-offset-gray-100"
+ >
<span class="flex w-full items-center justify-between">
<span class="flex min-w-0 items-center justify-between space-x-3">
<span class="flex min-w-0 flex-1 flex-col">
@@ -155,142 +166,151 @@
<span class="truncate text-sm text-gray-500">{session.profile.displayName}</span>
</span>
</span>
- <ChevronUpDownIcon class="flex-shrink-0 text-gray-400 group-hover:text-gray-500" aria-hidden="true" />
+ <ChevronUpDownIcon class="flex-shrink-0 text-gray-400 group-hover:text-gray-500" aria-hidden="true"/>
</span>
- </MenuButton>
- <Transition
- leave="transition ease-in duration-75"
- enter="transition ease-out duration-100"
- enterFrom="transform opacity-0 scale-95"
- enterTo="transform opacity-100 scale-100"
- leaveFrom="transform opacity-100 scale-100"
- leaveTo="transform opacity-0 scale-95"
- as="div"
- >
- <MenuItems
- class="absolute right-0 left-0 z-10 mt-1 origin-top divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
- >
- <div class="py-1">
- <MenuItem>
- <a href="/profile" class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> View profile </a>
- </MenuItem>
- <MenuItem>
- <a href="/settings" class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> Settings </a>
- </MenuItem>
- </div>
- <div class="py-1">
- <MenuItem>
+ </MenuButton>
+ <Transition
+ leave="transition ease-in duration-75"
+ enter="transition ease-out duration-100"
+ enterFrom="transform opacity-0 scale-95"
+ enterTo="transform opacity-100 scale-100"
+ leaveFrom="transform opacity-100 scale-100"
+ leaveTo="transform opacity-0 scale-95"
+ as="div"
+ >
+ <MenuItems
+ class="absolute right-0 left-0 z-10 mt-1 origin-top divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
+ >
+ <div class="py-1">
+ <MenuItem>
+ <a href="/profile"
+ class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100">
+ View profile </a>
+ </MenuItem>
+ <MenuItem>
+ <a href="/settings"
+ class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100">
+ Settings </a>
+ </MenuItem>
+ </div>
+ <div class="py-1">
+ <MenuItem>
<span
- on:click={() => sign_out()}
- class="text-gray-700 block px-4 py-2 text-sm hover:bg-red-200 hover:text-red-900 cursor-pointer"
+ on:click={() => sign_out()}
+ class="text-gray-700 block px-4 py-2 text-sm hover:bg-red-200 hover:text-red-900 cursor-pointer"
>
Sign out
</span>
- </MenuItem>
+ </MenuItem>
+ </div>
+ </MenuItems>
+ </Transition>
+ </Menu>
+ <!-- Sidebar Search -->
+ <div class="mt-3 hidden">
+ <label for="search" class="sr-only">Search</label>
+ <div class="relative mt-1 rounded-md shadow-sm">
+ <Input type="search" name="search" icon={MagnifyingGlassIcon} placeholder="Search"
+ bind:value={sidebarSearchValue}/>
+ </div>
</div>
- </MenuItems>
- </Transition>
- </Menu>
- <!-- Sidebar Search -->
- <div class="mt-3 hidden">
- <label for="search" class="sr-only">Search</label>
- <div class="relative mt-1 rounded-md shadow-sm">
- <Input type="search" name="search" icon={MagnifyingGlassIcon} placeholder="Search" bind:value={sidebarSearchValue} />
- </div>
- </div>
- <!-- Navigation -->
- <nav class="mt-5">
- <div class="space-y-1">
- {#each navigationItems as item}
- {@const current = $page.url.pathname.startsWith(item.href)}
- <a
- href={item.href}
- aria-current={current ? "page" : undefined}
- class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md
+ <!-- Navigation -->
+ <nav class="mt-5">
+ <div class="space-y-1">
+ {#each navigationItems as item}
+ {@const current = $page.url.pathname.startsWith(item.href)}
+ <a
+ href={item.href}
+ aria-current={current ? "page" : undefined}
+ class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md
{current ? 'bg-gray-200 text-gray-900' : 'text-gray-700 hover:text-gray-900 hover:bg-gray-50'}"
- >
- <svelte:component
- this={item.icon}
- class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}"
- aria-hidden="true"
- />
- {item.name}
- </a>
- {/each}
+ >
+ <svelte:component
+ this={item.icon}
+ class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}"
+ aria-hidden="true"
+ />
+ {item.name}
+ </a>
+ {/each}
+ </div>
+ </nav>
</div>
- </nav>
</div>
- </div>
- <!-- Main column -->
- <div class="flex flex-col lg:pl-64">
- <!-- Search header -->
- <div class="sticky top-0 z-10 flex h-16 flex-shrink-0 border-b border-gray-200 bg-white lg:hidden">
- <button
- type="button"
- class="border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-teal-500 lg:hidden"
- on:click={() => (sidebarOpen = true)}
- >
- <span class="sr-only">Open sidebar</span>
- <Bars3CenterLeftIcon aria-hidden="true" />
- </button>
- <div class="flex flex-1 justify-between px-4 sm:px-6 lg:px-8">
- <div class="flex flex-1">
- <form class="flex w-full md:ml-0" action="#" method="GET">
- <label for="search-field" class="sr-only">Search</label>
- <div class="relative w-full text-gray-400 focus-within:text-gray-600">
- <Input
- bind:value={sidebarSearchValue}
- icon={MagnifyingGlassIcon}
- id="search-field"
- name="search-field"
- placeholder="Search"
- type="search"
- />
- </div>
- </form>
- </div>
- <div class="flex items-center">
- <!-- Profile dropdown -->
- <Menu as="div" class="relative ml-3">
- <div>
- <MenuButton
- class="flex max-w-xs items-center rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2"
- >
- <span class="sr-only">Open user menu</span>
- </MenuButton>
- </div>
- <Transition
- enterFrom="transform opacity-0 scale-95"
- enterTo="transform opacity-100 scale-100"
- leaveFrom="transform opacity-100 scale-100"
- leaveTo="transform opacity-0 scale-95"
- as="div"
+ <!-- Main column -->
+ <div class="flex flex-col lg:pl-64">
+ <!-- Search header -->
+ <div class="sticky top-0 z-10 flex h-16 flex-shrink-0 border-b border-gray-200 bg-white lg:hidden">
+ <button
+ type="button"
+ class="border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-teal-500 lg:hidden"
+ on:click={() => (sidebarOpen = true)}
>
- <MenuItems
- class="absolute right-0 z-10 mt-2 w-48 origin-top-right divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
- >
- <div class="py-1">
- <MenuItem>
- <a href="/profile" class="text-gray-700 block px-4 py-2 text-sm"> View profile </a>
- </MenuItem>
- <MenuItem>
- <a href="/settings" class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> Settings </a>
- </MenuItem>
- <div class="py-1">
- <MenuItem>
- <span on:click={() => sign_out()} class="text-gray-700 block px-4 py-2 text-sm"> Sign out </span>
- </MenuItem>
- </div>
+ <span class="sr-only">Open sidebar</span>
+ <Bars3CenterLeftIcon aria-hidden="true"/>
+ </button>
+ <div class="flex flex-1 justify-between px-4 sm:px-6 lg:px-8">
+ <div class="flex flex-1">
+ <form class="flex w-full md:ml-0" action="#" method="GET">
+ <label for="search-field" class="sr-only">Search</label>
+ <div class="relative w-full text-gray-400 focus-within:text-gray-600">
+ <Input
+ bind:value={sidebarSearchValue}
+ icon={MagnifyingGlassIcon}
+ id="search-field"
+ name="search-field"
+ placeholder="Search"
+ type="search"
+ />
+ </div>
+ </form>
</div>
- </MenuItems>
- </Transition>
- </Menu>
+ <div class="flex items-center">
+ <!-- Profile dropdown -->
+ <Menu as="div" class="relative ml-3">
+ <div>
+ <MenuButton
+ class="flex max-w-xs items-center rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2"
+ >
+ <span class="sr-only">Open user menu</span>
+ </MenuButton>
+ </div>
+ <Transition
+ enterFrom="transform opacity-0 scale-95"
+ enterTo="transform opacity-100 scale-100"
+ leaveFrom="transform opacity-100 scale-100"
+ leaveTo="transform opacity-0 scale-95"
+ as="div"
+ >
+ <MenuItems
+ class="absolute right-0 z-10 mt-2 w-48 origin-top-right divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
+ >
+ <div class="py-1">
+ <MenuItem>
+ <a href="/profile" class="text-gray-700 block px-4 py-2 text-sm"> View
+ profile </a>
+ </MenuItem>
+ <MenuItem>
+ <a href="/settings"
+ class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100">
+ Settings </a>
+ </MenuItem>
+ <div class="py-1">
+ <MenuItem>
+ <span on:click={() => sign_out()}
+ class="text-gray-700 block px-4 py-2 text-sm"> Sign out </span>
+ </MenuItem>
+ </div>
+ </div>
+ </MenuItems>
+ </Transition>
+ </Menu>
+ </div>
+ </div>
</div>
- </div>
+ <main class="flex-1 p-3">
+ <slot/>
+ </main>
</div>
- <main class="flex-1 p-3">
- <slot />
- </main>
- </div>
</div>
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>
diff --git a/code/app/src/routes/(main)/(app)/projects/create/+page.svelte b/code/app/src/routes/(main)/(app)/projects/create/+page.svelte
index 1741506..ab38b2e 100644
--- a/code/app/src/routes/(main)/(app)/projects/create/+page.svelte
+++ b/code/app/src/routes/(main)/(app)/projects/create/+page.svelte
@@ -1,8 +1,8 @@
<script lang="ts">
- import { useSWR } from "sswr";
- import { Input, TextArea, Combobox, Button } from "$lib/components";
- import type { ProjectMember } from "$lib/models/projects/ProjectMember";
- import LL from "$lib/i18n/i18n-svelte";
+ import {useSWR} from "sswr";
+ import {Input, TextArea, Combobox, Button} from "$components";
+ import type {ProjectMember} from "$models/projects/ProjectMember";
+ import LL from "$i18n/i18n-svelte";
const formData = {
name: {
@@ -36,16 +36,16 @@
alert("Submitted");
}
- const { data: members } = useSWR("projectMembers");
+ const {data: members} = useSWR("projectMembers");
</script>
<h1>Create a new project</h1>
<form on:submit|preventDefault={submit_form_async} class="max-w-md flex flex-col gap-2">
- <Input label="Name" bind:value={formData.name.value} errors={formData.name.errors} required />
- <TextArea label="Description" bind:value={formData.description.value} errors={formData.description.errors} />
+ <Input label="Name" bind:value={formData.name.value} errors={formData.name.errors} required/>
+ <TextArea label="Description" bind:value={formData.description.value} errors={formData.description.errors}/>
<section class="grid grid-flow-row sm:grid-flow-col gap-2">
- <Input type="date" label="Start" bind:value={formData.start.value} errors={formData.start.errors} />
- <Input type="date" label="Stop" bind:value={formData.stop.value} errors={formData.stop.errors} />
+ <Input type="date" label="Start" bind:value={formData.start.value} errors={formData.start.errors}/>
+ <Input type="date" label="Stop" bind:value={formData.stop.value} errors={formData.stop.errors}/>
</section>
<Combobox options={$members} label={$LL.app.members()}>
<svelte:fragment slot="no-records">
@@ -57,5 +57,5 @@
{/if}
</svelte:fragment>
</Combobox>
- <Button text={$LL.submit()} />
+ <Button text={$LL.submit()}/>
</form>
diff --git a/code/app/src/routes/(main)/(app)/settings/+page.svelte b/code/app/src/routes/(main)/(app)/settings/+page.svelte
index 1f0cc67..1c9a910 100644
--- a/code/app/src/routes/(main)/(app)/settings/+page.svelte
+++ b/code/app/src/routes/(main)/(app)/settings/+page.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- import { Input, Button, Switch } from "$lib/components";
+ import {Input, Button, Switch} from "$components";
</script>
<div class="relative mx-auto max-w-4xl md:px-8 xl:px-0">
@@ -13,9 +13,9 @@
<div class="lg:hidden">
<label for="selected-tab" class="sr-only">Select a tab</label>
<select
- id="selected-tab"
- name="selected-tab"
- class="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-purple-500 focus:outline-none focus:ring-purple-500 sm:text-sm"
+ id="selected-tab"
+ name="selected-tab"
+ class="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-purple-500 focus:outline-none focus:ring-purple-500 sm:text-sm"
>
<option selected>General</option>
@@ -34,38 +34,39 @@
<div class="border-b border-gray-200">
<nav class="-mb-px flex space-x-8">
<!-- Current: "border-purple-500 text-purple-600", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" -->
- <a href="#" class="border-purple-500 text-purple-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
- >General</a
+ <a href="#"
+ class="border-purple-500 text-purple-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
+ >General</a
>
<a
- href="#"
- class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
- >Password</a
+ href="#"
+ class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
+ >Password</a
>
<a
- href="#"
- class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
- >Notifications</a
+ href="#"
+ class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
+ >Notifications</a
>
<a
- href="#"
- class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
- >Plan</a
+ href="#"
+ class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
+ >Plan</a
>
<a
- href="#"
- class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
- >Billing</a
+ href="#"
+ class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
+ >Billing</a
>
<a
- href="#"
- class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
- >Team Members</a
+ href="#"
+ class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
+ >Team Members</a
>
</nav>
</div>
@@ -87,9 +88,9 @@
<span class="flex-grow">Chelsea Hagon</span>
<span class="ml-4 flex-shrink-0">
<button
- type="button"
- class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
- >Update</button
+ type="button"
+ class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
+ >Update</button
>
</span>
</dd>
@@ -99,22 +100,22 @@
<dd class="mt-1 flex text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<span class="flex-grow">
<img
- class="h-8 w-8 rounded-full"
- src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
- alt=""
+ class="h-8 w-8 rounded-full"
+ src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
+ alt=""
/>
</span>
<span class="ml-4 flex flex-shrink-0 items-start space-x-4">
<button
- type="button"
- class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
- >Update</button
+ type="button"
+ class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
+ >Update</button
>
<span class="text-gray-300" aria-hidden="true">|</span>
<button
- type="button"
- class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
- >Remove</button
+ type="button"
+ class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
+ >Remove</button
>
</span>
</dd>
@@ -125,9 +126,9 @@
<span class="flex-grow">chelsea.hagon@example.com</span>
<span class="ml-4 flex-shrink-0">
<button
- type="button"
- class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
- >Update</button
+ type="button"
+ class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
+ >Update</button
>
</span>
</dd>
@@ -138,9 +139,9 @@
<span class="flex-grow">Human Resources Manager</span>
<span class="ml-4 flex-shrink-0">
<button
- type="button"
- class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
- >Update</button
+ type="button"
+ class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
+ >Update</button
>
</span>
</dd>
@@ -152,7 +153,8 @@
<div class="mt-10 divide-y divide-gray-200">
<div class="space-y-1">
<h3 class="text-lg font-medium leading-6 text-gray-900">Account</h3>
- <p class="max-w-2xl text-sm text-gray-500">Manage how information is displayed on your account.</p>
+ <p class="max-w-2xl text-sm text-gray-500">Manage how information is displayed on your
+ account.</p>
</div>
<div class="mt-6">
<dl class="divide-y divide-gray-200">
@@ -162,9 +164,9 @@
<span class="flex-grow">English</span>
<span class="ml-4 flex-shrink-0">
<button
- type="button"
- class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
- >Update</button
+ type="button"
+ class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
+ >Update</button
>
</span>
</dd>
@@ -175,22 +177,24 @@
<span class="flex-grow">DD-MM-YYYY</span>
<span class="ml-4 flex flex-shrink-0 items-start space-x-4">
<button
- type="button"
- class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
- >Update</button
+ type="button"
+ class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
+ >Update</button
>
<span class="text-gray-300" aria-hidden="true">|</span>
<button
- type="button"
- class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
- >Remove</button
+ type="button"
+ class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
+ >Remove</button
>
</span>
</dd>
</div>
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:pt-5">
- <dt class="text-sm font-medium text-gray-500" id="timezone-option-label">Automatic timezone</dt>
- <Switch />
+ <dt class="text-sm font-medium text-gray-500" id="timezone-option-label">Automatic
+ timezone
+ </dt>
+ <Switch/>
</div>
</dl>
</div>